Skip to content

Commit

Permalink
fix: major error in batch processing
Browse files Browse the repository at this point in the history
* a multiple MSH segment is a batch
* moved split and getSegIndex to utils.ts
* updated unit testing for code fixes
  • Loading branch information
Bugs5382 committed Jan 10, 2024
1 parent b22a9f3 commit b303d01
Show file tree
Hide file tree
Showing 7 changed files with 113 additions and 54 deletions.
14 changes: 13 additions & 1 deletion __tests__/hl7.build.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -921,7 +921,7 @@ describe('node hl7 client - builder tests', () => {
msh_10: 'CONTROL_ID'
}
})
expect(isBatch(message.toString())).toBe(false)
expect(isBatch(message.toString())).toBe(true)
})

test('...isBatch -should be false', async () => {
Expand All @@ -948,10 +948,12 @@ describe('node hl7 client - builder tests', () => {

describe('parse message, batch, and file', () => {
const hl7_string: string = 'MSH|^~\\&|||||20081231||ADT^A01^ADT_A01|12345||2.7\rEVN||20081231'
const hl7_batch_msh_string: string = 'MSH|^~\\&|||||20081231||ADT^A01^ADT_A01|12345||2.7\rEVN||20081231\rMSH|^~\\&|||||20081231||ADT^A01^ADT_A01|12345||2.7\rEVN||20081231'
const hl7_non_standard: string = 'MSH:-+?*:field2:field3component1-field3component2:field4repeat1+field4repeat2:field5subcomponent1*field5subcomponent2:field6?R?'
const hl7_batch: string = 'BHS|^~\\&|||||20231208\rMSH|^~\\&|||||20231208||ADT^A01^ADT_A01|CONTROL_ID||2.7\rEVN||20081231\rEVN||20081231\rBTS|1'
const hl7_batch_non_standard: string = 'BHS:-+?*:::::20231208\rMSH|^~\\&|||||20231208||ADT^A01^ADT_A01|CONTROL_ID||2.7\rEVN||20081231\rEVN||20081231\rBTS|1'


test('...verify MSH input', async () => {
const message = new Message({ text: hl7_string })

Expand Down Expand Up @@ -1012,6 +1014,16 @@ describe('node hl7 client - builder tests', () => {
expect(count).toBe(2)
})
})

test('...should be used as a batch', async () => {
try {
// @ts-expect-error
const message = new Message({ text: hl7_batch_msh_string })
}catch (err) {
expect(err).toEqual(new Error('Multiple MSH segments found. Use Batch.'))
}
})

})

describe('file tests', () => {
Expand Down
26 changes: 20 additions & 6 deletions __tests__/hl7.end2end.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -627,8 +627,12 @@ describe('node hl7 end to end - client', () => {

describe('...send file with two message, get proper ACK', () => {
let LISTEN_PORT: number
let fileName: string

const hl7_string: string = 'MSH|^~\\&|||||20081231||ADT^A01^ADT_A01|12345|D|2.7\rEVN||20081231\rMSH|^~\\&|||||20081231||ADT^A01^ADT_A01|12345|D|2.7\rEVN||20081231'
const hl7_string: string[] = [
"MSH|^~\\&|||||20081231||ADT^A01^ADT_A01|12345|D|2.7\rEVN||20081231",
"MSH|^~\\&|||||20081231||ADT^A01^ADT_A01|12345|D|2.7\rEVN||20081231"
]

beforeAll(async () => {
fs.readdir('temp/', (err, files) => {
Expand All @@ -642,18 +646,28 @@ describe('node hl7 end to end - client', () => {

await sleep(2)

const message = new Message({ text: hl7_string, date: '8' })
message.toFile('readFileTestMSH', true, 'temp/')
const batch = new Batch()

fs.access('temp/hl7.readFileTestMSH.20081231.hl7', fs.constants.F_OK, (err) => {
batch.start()

for (let i = 0; i < hl7_string.length; i++) {
const message = new Message({text: hl7_string[i] })
batch.add(message)
}

batch.end()

fileName = batch.toFile('readFileTestMSH', true, 'temp/')

fs.access(`temp/${fileName as string}`, fs.constants.F_OK, (err) => {
if (err == null) {
// Do something
}
})

await (async () => {
try {
await fs.promises.access('temp/hl7.readFileTestMSH.20081231.hl7', fs.constants.F_OK)
await fs.promises.access(`temp/${fileName as string}`, fs.constants.F_OK)
// Do something
} catch (err) {
// Handle error
Expand Down Expand Up @@ -693,7 +707,7 @@ describe('node hl7 end to end - client', () => {

await expectEvent(OB_ADT, 'client.connect')

const fileBatch = await OB_ADT.readFile('temp/hl7.readFileTestMSH.20081231.hl7')
const fileBatch = await OB_ADT.readFile(`temp/${fileName as string}`)

await OB_ADT.sendMessage(fileBatch)

Expand Down
8 changes: 5 additions & 3 deletions src/builder/batch.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { HL7FatalError, HL7ParserError } from '../utils/exception.js'
import { ClientBuilderOptions, normalizedClientBatchBuilderOptions } from '../utils/normalizedBuilder.js'
import { createHL7Date } from '../utils/utils.js'
import { createHL7Date, split } from '../utils/utils.js'
import { FileBatch } from './fileBatch.js'
import { HL7Node } from './interface/hL7Node.js'
import { Message } from './message.js'
Expand Down Expand Up @@ -36,7 +36,7 @@ export class Batch extends RootBase {
this._messagesCount = 0

if (typeof opt.text !== 'undefined' && opt.parsing === true && opt.text !== '') {
this._lines = this.split(opt.text).filter(line => line.startsWith('MSH'))
this._lines = split(opt.text).filter(line => line.startsWith('MSH'))
} else {
this.set('BHS.7', createHL7Date(new Date(), this._opt.date))
}
Expand Down Expand Up @@ -194,7 +194,7 @@ export class Batch extends RootBase {
* ```
* You can set an `extension` parameter on Batch to set a custom extension if you don't want to be HL7.
*/
toFile (name: string, newLine?: boolean, location?: string, extension: string = 'hl7'): void {
toFile (name: string, newLine?: boolean, location?: string, extension: string = 'hl7'): string {
const fileBatch = new FileBatch({ location, newLine: newLine === true ? '\n' : '', extension })
fileBatch.start()

Expand All @@ -210,6 +210,8 @@ export class Batch extends RootBase {
fileBatch.end()

fileBatch.createFile(name)

return fileBatch.fileName()
}

/** @internal */
Expand Down
14 changes: 12 additions & 2 deletions src/builder/fileBatch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import fs from 'node:fs'
import path from 'node:path'
import { HL7FatalError, HL7ParserError } from '../utils/exception.js'
import { ClientBuilderFileOptions, normalizedClientFileBuilderOptions } from '../utils/normalizedBuilder.js'
import { createHL7Date } from '../utils/utils.js'
import { createHL7Date, split } from '../utils/utils.js'
import { Batch } from './batch.js'
import { HL7Node } from './interface/hL7Node.js'
import { Message } from './message.js'
Expand Down Expand Up @@ -44,9 +44,10 @@ export class FileBatch extends RootBase {
this._opt = opt
this._batchCount = 0
this._messagesCount = 0
this._fileName = ''

if (typeof opt.text !== 'undefined' && opt.parsing === true && opt.text !== '') {
this._lines = this.split(opt.text).filter(line => line.startsWith('MSH'))
this._lines = split(opt.text).filter(line => line.startsWith('MSH'))
} else {
this.set('FHS.7', createHL7Date(new Date(), this._opt.date))
}
Expand Down Expand Up @@ -130,6 +131,15 @@ export class FileBatch extends RootBase {
segment.set('1', this._batchCount + this._messagesCount)
}

/**
* Get File name
* @description Get File name going to be created.
* @since 1.2.0
*/
fileName (): string {
return this._fileName
}

/**
* Get FHS Segment at Path
* @since 1.0.0
Expand Down
11 changes: 9 additions & 2 deletions src/builder/message.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { HL7FatalError } from '../utils/exception.js'
import { HL7FatalError, HL7ParserError } from '../utils/exception.js'
import { ClientBuilderMessageOptions, normalizedClientMessageBuilderOptions } from '../utils/normalizedBuilder.js'
import { isHL7Number } from '../utils/utils.js'
import { isHL7Number, split } from '../utils/utils.js'
import { FileBatch } from './fileBatch.js'
import { NodeBase } from './modules/nodeBase.js'
import { RootBase } from './modules/rootBase.js'
Expand Down Expand Up @@ -41,6 +41,13 @@ export class Message extends RootBase {

this._opt = opt

if (typeof opt.text !== 'undefined' && opt.parsing === true && opt.text !== '') {
const totalMsh = split(opt.text).filter(line => line.startsWith('MSH'))
if (totalMsh.length !== 0 && totalMsh.length !== 1) {
throw new HL7ParserError(500, 'Multiple MSH segments found. Use Batch.')
}
}

if (typeof this._opt.messageHeader !== 'undefined') {
if (this._opt.specification.checkMSH(this._opt.messageHeader) === true) {
this._opt.specification.buildMSH(this._opt.messageHeader, this)
Expand Down
37 changes: 0 additions & 37 deletions src/builder/modules/rootBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,21 +100,6 @@ export class RootBase extends NodeBase {
})
}

/** @internal */
split (data: string, segments: string[] = []): string[] {
const getSegIndex = [...this._getSegIndexes(['FHS', 'BHS', 'MSH', 'BTS', 'FTS'], data)]
getSegIndex.sort((a, b) => parseInt(a) - parseInt(b))
for (let i = 0; i < getSegIndex.length; i++) {
const start = parseInt(getSegIndex[i])
let end = parseInt(getSegIndex[i + 1])
if (i + 1 === getSegIndex.length) {
end = data.length
}
segments.push(data.slice(start, end))
}
return segments
}

/** @internal */
unescape (text: string): string {
if (text === null) {
Expand Down Expand Up @@ -153,26 +138,4 @@ export class RootBase extends NodeBase {
return ''
})
}

/** @internal */
private _getSegIndexes (names: string[], data: string, list: string[] = []): string[] {
for (let i = 0; i < names.length; i++) {
const regex = new RegExp(`(\\n|\\r|)(${names[i]})\\|`, 'g')
let m
while ((m = regex.exec(data)) != null) {
const s = m[0]
if (s.includes('\r\n')) {
m.index = m.index + 2
} else if (s.includes('\n')) {
m.index++
} else if (s.includes('\r')) {
m.index++
}
if (m.index !== null) {
list.push(m.index.toString())
}
}
}
return list
}
}
57 changes: 54 additions & 3 deletions src/utils/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,16 +83,17 @@ export const expBackoff = (step: number, high: number, attempts: number, exp = 2
}

/**
* Check to see if the message is a Batch (BHS)
* Check to see if the message starts with Batch (BHS) header segment.
* @since 1.0.0
* @param message
*/
export const isBatch = (message: string): boolean => {
return message.startsWith('BHS')
const lines = split(message).filter(line => line.startsWith('MSH')).length > 0 || false
return message.startsWith('BHS') || lines
}

/**
* Check to see if the message is a File Batch (FHS)
* Check to see if the message starts with a a File Batch (FHS) header segment.
* @param message
*/
export const isFile = (message: string): boolean => {
Expand Down Expand Up @@ -172,3 +173,53 @@ export const randomString = (length = 20): string => {
}
return result
}

/**
* Split the message.
* @description Split the message into its parts.
* @since 1.0.0
* @param data
* @param segments
*/
export const split = (data: string, segments: string[] = []): string[] => {
const getSegIndex = [...getSegIndexes(['FHS', 'BHS', 'MSH', 'BTS', 'FTS'], data)]
getSegIndex.sort((a, b) => parseInt(a) - parseInt(b))
for (let i = 0; i < getSegIndex.length; i++) {
const start = parseInt(getSegIndex[i])
let end = parseInt(getSegIndex[i + 1])
if (i + 1 === getSegIndex.length) {
end = data.length
}
segments.push(data.slice(start, end))
}
return segments
}

/**
* Get Segment Indexes
* @description Helper for {@link split}
* @since 1.0.0
* @param names
* @param data
* @param list
*/
const getSegIndexes = (names: string[], data: string, list: string[] = []): string[] => {
for (let i = 0; i < names.length; i++) {
const regex = new RegExp(`(\\n|\\r|)(${names[i]})\\|`, 'g')
let m
while ((m = regex.exec(data)) != null) {
const s = m[0]
if (s.includes('\r\n')) {
m.index = m.index + 2
} else if (s.includes('\n')) {
m.index++
} else if (s.includes('\r')) {
m.index++
}
if (m.index !== null) {
list.push(m.index.toString())
}
}
}
return list
}

0 comments on commit b303d01

Please sign in to comment.