TOC
- X12Core
- X12Formatting
- X12Generator
- X12Mapping
- X12ObjectModel
- X12Parser
- X12QueryEngine
- X12ValidationEngine
X12Core
should export members.
if (!Object.keys(core).includes('X12Parser')) {
throw new Error('X12 core is missing X12Parser.')
}
should create ArgumentNullError.
const error = new Errors_1.ArgumentNullError('test')
if (error.message !== "The argument, 'test', cannot be null.") {
throw new Error('ArgumentNullError did not return the correct message.')
}
should create GeneratorError.
const error = new Errors_1.GeneratorError('test')
if (error.message !== 'test') {
throw new Error('GeneratorError did not return the correct message.')
}
should create ParserError.
const error = new Errors_1.ParserError('test')
if (error.message !== 'test') {
throw new Error('ParserError did not return the correct message.')
}
should create QuerySyntaxError.
const error = new Errors_1.QuerySyntaxError('test')
if (error.message !== 'test') {
throw new Error('QuerySyntaxError did not return the correct message.')
}
should create X12Diagnostic.
const diag = new X12Diagnostic_1.X12Diagnostic()
if (!(diag instanceof X12Diagnostic_1.X12Diagnostic)) {
throw new Error('Could not create X12Diagnostic.')
}
X12Formatting
should replicate the source data unless changes are made.
const edi = fs.readFileSync('test/test-data/850.edi', 'utf8')
const parser = new core_1.X12Parser(true)
const interchange = parser.parse(edi)
const edi2 = interchange.toString()
if (edi !== edi2) {
throw new Error(`Formatted EDI does not match source. Found ${edi2}, expected ${edi}.`)
}
should replicate the source data for a fat interchange unless changes are made.
const edi = fs.readFileSync('test/test-data/850_fat.edi', 'utf8')
const parser = new core_1.X12Parser(true)
const interchange = parser.parse(edi)
const options = {
format: true,
endOfLine: '\n'
}
const edi2 = interchange.toString(options)
if (edi !== edi2) {
throw new Error(`Formatted EDI does not match source. Found ${edi2}, expected ${edi}.`)
}
X12Generator
should create X12Generator.
const generator = new core_1.X12Generator()
const notation = generator.getJSEDINotation()
const options = generator.getOptions()
generator.setJSEDINotation(new core_1.JSEDINotation())
generator.setOptions({})
if (!(notation instanceof core_1.JSEDINotation) || typeof options !== 'object') {
throw new Error('Could not correctly create instance of X12Generator.')
}
should replicate the source data unless changes are made.
const edi = fs.readFileSync('test/test-data/850.edi', 'utf8')
const parser = new core_1.X12Parser(true)
const notation = parser.parse(edi).toJSEDINotation()
const generator = new core_1.X12Generator(notation)
const edi2 = generator.toString()
if (edi !== edi2) {
throw new Error(`Formatted EDI does not match source. Found ${edi2}, expected ${edi}.`)
}
should replicate the source data to and from JSON unless changes are made.
const edi = fs.readFileSync('test/test-data/850.edi', 'utf8')
const parser = new core_1.X12Parser(true)
const interchange = parser.parse(edi)
const json = JSON.stringify(interchange)
const generator = new core_1.X12Generator(JSON.parse(json))
const edi2 = generator.toString()
if (edi !== edi2) {
throw new Error(`Formatted EDI does not match source. Found ${edi2}, expected ${edi}.`)
}
should not generate 271 with 3 ST elements using default segment headers.
const fileEdi = fs.readFileSync('test/test-data/271.edi', 'utf8').split('~')
const i = new core_1.X12Interchange()
i.setHeader(fileEdi[0].split('*').slice(1))
const fg = i.addFunctionalGroup()
fg.setHeader(fileEdi[1].split('*').slice(1))
const t = fg.addTransaction()
let error
try {
t.setHeader(fileEdi[2].split('*').slice(1))
} catch (err) {
error = err.message
}
if (error !== 'Segment "ST" with 3 elements does meet the required count of 2.') {
throw new Error('271 with 3 ST elements parsing succeed which should not happen')
}
should generate 271 with 3 ST elements using custom segment headers.
const fileEdi = fs.readFileSync('test/test-data/271.edi', 'utf8').split('~')
const i = new core_1.X12Interchange({
segmentHeaders: [
core_1.ISASegmentHeader,
core_1.GSSegmentHeader,
{
tag: 'ST',
layout: {
ST01: 3,
ST02: 9,
ST02_MIN: 4,
ST03: 35,
ST03_MIN: 1,
COUNT: 3,
PADDING: false
}
}
]
})
i.setHeader(fileEdi[0].split('*').slice(1))
const fg = i.addFunctionalGroup()
fg.setHeader(fileEdi[1].split('*').slice(1))
const t = fg.addTransaction()
t.setHeader(fileEdi[2].split('*').slice(1))
should validate custom segment headers.
const edi = fs.readFileSync('test/test-data/271.edi', 'utf8')
const options = {
segmentHeaders: [
core_1.ISASegmentHeader,
core_1.GSSegmentHeader,
{
tag: 'ST',
layout: {
ST01: 3,
ST02: 9,
ST02_MIN: 4,
ST03: 35,
ST03_MIN: 1,
COUNT: 3,
PADDING: false
}
},
{
tag: 'HL',
layout: {
HL01: 3,
HL02: 9,
HL02_MIN: 4,
HL03: 35,
HL03_MIN: 1,
COUNT: 3,
PADDING: false
}
}
]
}
const parser = new core_1.X12Parser(true)
const interchange = parser.parse(edi)
const json = JSON.stringify(interchange)
let error
try {
const generator = new core_1.X12Generator(JSON.parse(json), options)
generator.toString()
} catch (err) {
error = err.message
}
if (error !== 'Segment "HL" with 4 elements does meet the required count of 3.') {
throw new Error('271 with custom segment headers parsing succeed which should not happen')
}
X12Mapping
should map transaction to data.
const parser = new core_1.X12Parser()
const interchange = parser.parse(edi)
const transaction = interchange.functionalGroups[0].transactions[0]
const mapper = new core_1.X12TransactionMap(JSON.parse(mapJson), transaction)
const result = JSON.stringify(mapper.toObject())
if (result !== resultJson) {
throw new Error(`Formatted JSON does not match source. Found ${result}, expected ${resultJson}.`)
}
should map data to transaction with custom macro.
const transaction = new core_1.X12Transaction()
const mapper = new core_1.X12TransactionMap(JSON.parse(transactionJson), transaction)
const data = JSON.parse(transactionData)
const result = mapper.fromObject(data, {
toFixed: function toFixed (key, places) {
return {
val: parseFloat(key).toFixed(places)
}
}
})
if (!(result instanceof core_1.X12Transaction)) {
throw new Error(`An error occured when mapping an object to a transaction.`)
}
should map data to transaction with LiquidJS.
const transaction = new core_1.X12Transaction()
const mapper = new core_1.X12TransactionMap(JSON.parse(transactionJsonLiquid), transaction, 'liquidjs')
const data = JSON.parse(transactionData)
const result = mapper.fromObject(data, {
to_fixed: (value, places) => parseFloat(value).toFixed(places)
})
if (!(result instanceof core_1.X12Transaction)) {
throw new Error(`An error occured when mapping an object to a transaction.`)
}
X12ObjectModel
should create X12Interchange with string delimiters.
const interchange = new core_1.X12Interchange('~', '*')
if (interchange.elementDelimiter !== '*') {
throw new Error('Instance of X12Interchange not successfully created.')
}
should create X12FatInterchange.
const parser = new core_1.X12Parser()
const interchange = parser.parse(edi)
const fatInterchange = new core_1.X12FatInterchange([interchange])
const str = fatInterchange.toString()
const json = fatInterchange.toJSON()
if (!Array.isArray(json) || typeof str !== 'string') {
throw new Error('Instance of X12FatInterchange not successfully created.')
}
should create X12Segment.
const segment = new core_1.X12Segment()
const noElement = segment.replaceElement('1', 1)
const noInsert = segment.insertElement('1', 1)
const noneToRemove = segment.removeElement(1)
const defaultVal = segment.valueOf(1, '2')
segment.setTag('WX')
segment.addElement('1')
segment.insertElement('2', 1)
segment.removeElement(2)
if (
noElement !== null ||
noInsert !== null ||
noneToRemove !== false ||
defaultVal !== '2' ||
segment.elements.length !== 1 ||
segment.elements[0].value !== '2'
) {
throw new Error('Instance of segment or methods did not execute as expected.')
}
should cast functional group to JSON.
const parser = new core_1.X12Parser()
const interchange = parser.parse(edi)
const functionalGroup = interchange.functionalGroups[0]
if (typeof functionalGroup.toJSON() !== 'object') {
throw new Error('Instance of X12FunctionalGroup not cast to JSON.')
}
should cast transaction set to JSON.
const parser = new core_1.X12Parser()
const interchange = parser.parse(edi)
const functionalGroup = interchange.functionalGroups[0]
const transaction = functionalGroup.transactions[0]
if (typeof transaction.toJSON() !== 'object') {
throw new Error('Instance of X12FunctionalGroup not cast to JSON.')
}
should cast segment to JSON.
const parser = new core_1.X12Parser()
const interchange = parser.parse(edi)
const functionalGroup = interchange.functionalGroups[0]
const transaction = functionalGroup.transactions[0]
const segment = transaction.segments[0]
if (typeof segment.toJSON() !== 'object') {
throw new Error('Instance of X12FunctionalGroup not cast to JSON.')
}
should construct JSEDINotation objects.
const notation = new core_1.JSEDINotation()
const group = new JSEDINotation_1.JSEDIFunctionalGroup()
const transaction = new JSEDINotation_1.JSEDITransaction()
if (
!(notation instanceof core_1.JSEDINotation) ||
!(group instanceof JSEDINotation_1.JSEDIFunctionalGroup) ||
!(transaction instanceof JSEDINotation_1.JSEDITransaction)
) {
throw new Error('One or more JS EDI Notation objects could not be constructed.')
}
X12Parser
should parse a valid X12 document without throwing an error.
const edi = fs.readFileSync('test/test-data/850.edi', 'utf8')
const parser = new core_1.X12Parser()
parser.parse(edi)
should parse a fat X12 document without throwing an error.
const edi = fs.readFileSync('test/test-data/850_fat.edi', 'utf8')
const parser = new core_1.X12Parser(true)
parser.parse(edi)
should parse and reconstruct a valid X12 stream without throwing an error.
;async () => {
return new Promise((resolve, reject) => {
const ediStream = fs.createReadStream('test/test-data/850.edi', 'utf8')
const parser = new core_1.X12Parser()
const segments = []
ediStream.on('error', error => {
reject(error)
})
parser.on('error', error => {
reject(error)
})
ediStream
.pipe(parser)
.on('data', data => {
segments.push(data)
})
.on('end', () => {
const edi = fs.readFileSync('test/test-data/850.edi', 'utf8')
const interchange = parser.getInterchangeFromSegments(segments)
if (interchange.toString() !== edi) {
reject(new Error('Expected parsed EDI stream to match raw EDI document.'))
}
resolve()
})
})
}
should produce accurate line numbers for files with line breaks.
const edi = fs.readFileSync('test/test-data/850_3.edi', 'utf8')
const parser = new core_1.X12Parser()
const interchange = parser.parse(edi)
const segments = [].concat(
[interchange.header, interchange.functionalGroups[0].header, interchange.functionalGroups[0].transactions[0].header],
interchange.functionalGroups[0].transactions[0].segments,
[
interchange.functionalGroups[0].transactions[0].trailer,
interchange.functionalGroups[0].trailer,
interchange.trailer
]
)
for (let i = 0; i < segments.length; i++) {
const segment = segments[i]
if (i !== segment.range.start.line) {
throw new Error(`Segment line number incorrect. Expected ${i}, found ${segment.range.start.line}.`)
}
}
should throw an ArgumentNullError.
const parser = new core_1.X12Parser()
let error
try {
parser.parse(undefined)
} catch (err) {
error = err
}
if (error.name !== 'ArgumentNullError') {
throw new Error('ArgumentNullError expected when first argument to X12Parser.parse() is undefined.')
}
should throw an ParserError.
const parser = new core_1.X12Parser(true)
let error
try {
parser.parse('')
} catch (err) {
error = err
}
if (error.name !== 'ParserError') {
throw new Error('ParserError expected when document length is too short and parser is strict.')
}
should find mismatched elementDelimiter.
const edi = fs.readFileSync('test/test-data/850.edi', 'utf8')
const parser = new core_1.X12Parser(true)
let error
try {
parser.parse(edi, { elementDelimiter: '+' })
} catch (err) {
error = err
}
if (error.name !== 'ParserError') {
throw new Error('ParserError expected when elementDelimiter in document does not match and parser is strict.')
}
X12QueryEngine
should handle basic element references.
const edi = fs.readFileSync('test/test-data/850.edi', 'utf8')
const parser = new core_1.X12Parser(true)
const engine = new core_1.X12QueryEngine(parser)
const results = engine.query(edi, 'REF02')
if (results.length !== 2) {
throw new Error('Expected two matching elements for REF02.')
}
should handle qualified element references.
const edi = fs.readFileSync('test/test-data/850.edi', 'utf8')
const parser = new core_1.X12Parser(true)
const engine = new core_1.X12QueryEngine(parser)
const results = engine.query(edi, 'REF02:REF01["DP"]')
if (results.length !== 1) {
throw new Error('Expected one matching element for REF02:REF01["DP"].')
} else if (results[0].value !== '038') {
throw new Error('Expected REF02 to be "038".')
}
should handle segment path element references.
const edi = fs.readFileSync('test/test-data/850.edi', 'utf8')
const parser = new core_1.X12Parser(true)
const engine = new core_1.X12QueryEngine(parser)
const results = engine.query(edi, 'PO1-PID05:PID01["F"]')
if (results.length !== 6) {
throw new Error(`Expected six matching elements for PO1-PID05:PID01["F"]; received ${results.length}.`)
}
should handle HL path element references.
const edi = fs.readFileSync('test/test-data/856.edi', 'utf8')
const parser = new core_1.X12Parser(true)
const engine = new core_1.X12QueryEngine(parser)
const results = engine.query(edi, 'HL+S+O+I-LIN03')
if (results[0].value !== '87787D' || results[1].value !== '99887D') {
throw new Error('Expected two matching elements for HL+S+O+I-LIN03.')
}
should handle HL paths where HL03 is a number.
const edi = fs.readFileSync('test/test-data/271.edi', 'utf8')
const parser = new core_1.X12Parser(true)
const engine = new core_1.X12QueryEngine(parser)
const results = engine.query(edi, 'HL+20+21+22-NM101')
if (results.length !== 2) {
throw new Error('Expected two matching elements for HL+20+21+22-NM101.')
}
should handle FOREACH macro references.
const edi = fs.readFileSync('test/test-data/850.edi', 'utf8')
const parser = new core_1.X12Parser(true)
const engine = new core_1.X12QueryEngine(parser)
const result = engine.querySingle(edi, 'FOREACH(PO1)=>PID05:PID01["F"]')
if (result.values.length !== 6) {
throw new Error(
`Expected six matching elements for FOREACH(PO1)=>PID05:PID01["F"]; received ${result.values.length}.`
)
}
should handle CONCAT macro references.
const edi = fs.readFileSync('test/test-data/850.edi', 'utf8')
const parser = new core_1.X12Parser(true)
const engine = new core_1.X12QueryEngine(parser)
const result = engine.querySingle(edi, 'CONCAT(REF02:REF01["DP"], & )=>REF02:REF01["PS"]')
if (result.value !== '038 & R') {
throw new Error(`Expected '038 & R'; received '${result.value}'.`)
}
should return valid range information for segments and elements.
const edi = fs.readFileSync('test/test-data/850.edi', 'utf8')
const parser = new core_1.X12Parser(true)
const engine = new core_1.X12QueryEngine(parser)
const result = engine.querySingle(edi, 'BEG03')
if (result.segment.range.start.line !== 3) {
throw new Error(`Start line for segment is incorrect; found ${result.segment.range.start.line}, expected 3.`)
}
if (result.segment.range.start.character !== 0) {
throw new Error(`Start char for segment is incorrect; found ${result.segment.range.start.character}, expected 0.`)
}
if (result.element.range.start.line !== 3) {
throw new Error(`Start line for element is incorrect; found ${result.element.range.start.line}, expected 3.`)
}
if (result.element.range.start.character !== 10) {
throw new Error(`Start char for element is incorrect; found ${result.element.range.start.character}, expected 10.`)
}
if (result.segment.range.end.line !== 3) {
throw new Error(`End line for segment is incorrect; found ${result.segment.range.end.line}, expected 3.`)
}
if (result.segment.range.end.character !== 41) {
throw new Error(`End char for segment is incorrect; found ${result.segment.range.end.character}, expected 41.`)
}
if (result.element.range.end.line !== 3) {
throw new Error(`End line for element is incorrect; found ${result.element.range.end.line}, expected 3.`)
}
if (result.element.range.end.character !== 20) {
throw new Error(`End char for element is incorrect; found ${result.element.range.end.character}, expected 20.`)
}
should handle envelope queries.
const edi = fs.readFileSync('test/test-data/850.edi', 'utf8')
const parser = new core_1.X12Parser(true)
const engine = new core_1.X12QueryEngine(parser)
const results = engine.query(edi, 'ISA06')
if (results.length === 1) {
if (results[0].value.trim() !== '4405197800') {
throw new Error(`Expected 4405197800, found ${results[0].value}.`)
}
} else {
throw new Error(`Expected exactly one result. Found ${results.length}.`)
}
should handle queries for files with line feed segment terminators.
const edi = fs.readFileSync('test/test-data/850_2.edi', 'utf8')
const parser = new core_1.X12Parser(true)
const engine = new core_1.X12QueryEngine(parser)
const result = engine.querySingle(edi, 'REF02:REF01["DP"]')
if (result.value.trim() !== '038') {
throw new Error(`Expected 038, found ${result.value}.`)
}
should handle chained qualifiers.
const edi = fs.readFileSync('test/test-data/850.edi', 'utf8')
const parser = new core_1.X12Parser(true)
const engine = new core_1.X12QueryEngine(parser)
const results = engine.query(edi, 'REF02:REF01["DP"]:BEG02["SA"]')
if (results.length === 1) {
if (results[0].value.trim() !== '038') {
throw new Error(`Expected 038, found ${results[0].value}.`)
}
} else {
throw new Error(`Expected exactly one result. Found ${results.length}.`)
}
X12ValidationEngine
should create validation rule.
const rule = new X12ValidationEngine_1.X12ValidationRule({ engine: /ab+c/gu })
assert.deepStrictEqual(rule instanceof X12ValidationEngine_1.X12ValidationRule, true)
should create validation rule from JSON.
const ruleJson = JSON.parse(validationRule850)
const rule = new X12ValidationEngine_1.X12InterchangeRule(ruleJson)
const stringJson = JSON.stringify(rule)
assert.deepStrictEqual(JSON.parse(stringJson), ruleJson)
// fs.writeFileSync('test/test-data/850_validation.rule.json', JSON.stringify(rule, null, 2))
should create validation rule regardless of header or trailer.
const ruleJson = JSON.parse(validationRuleNoHeader850)
const rule = new X12ValidationEngine_1.X12InterchangeRule(ruleJson)
assert.deepStrictEqual(rule instanceof X12ValidationEngine_1.X12InterchangeRule, true)
// fs.writeFileSync('test/test-data/850_validation.rule.json', JSON.stringify(rule, null, 2))
should validate X12 document.
const ruleJson = JSON.parse(validationRuleSimple850)
const parser = new core_1.X12Parser()
const interchange = parser.parse(edi)
const validator = new core_1.X12ValidationEngine()
let rule = new X12ValidationEngine_1.X12InterchangeRule(ruleJson)
let report = validator.assert(interchange, rule)
assert.strictEqual(report, true)
rule = new X12ValidationEngine_1.X12GroupRule(ruleJson.group)
report = validator.assert(interchange.functionalGroups[0], rule)
assert.strictEqual(report, true)
rule = new X12ValidationEngine_1.X12TransactionRule(ruleJson.group.transaction)
report = validator.assert(interchange.functionalGroups[0].transactions[0], rule)
assert.strictEqual(report, true)
rule = new X12ValidationEngine_1.X12SegmentRule(ruleJson.group.transaction.segments[0])
report = validator.assert(interchange.functionalGroups[0].transactions[0].segments[0], rule)
assert.strictEqual(report, true)
rule = new X12ValidationEngine_1.X12ElementRule(ruleJson.group.transaction.segments[0].elements[0])
report = validator.assert(interchange.functionalGroups[0].transactions[0].segments[0].elements[0], rule)
assert.strictEqual(report, true)
should invalidate X12 document.
const ruleJson = JSON.parse(validationRuleSimple850)
const parser = new core_1.X12Parser()
const interchange = parser.parse(edi2)
const rule = new X12ValidationEngine_1.X12InterchangeRule(ruleJson)
const validator = new core_1.X12ValidationEngine({
throwError: true,
acknowledgement: {
isa: new core_1.X12Segment('ISA').setElements([
'00',
'',
'00',
'',
'ZZ',
'TEST1',
'ZZ',
'TEST2',
'200731',
'0430',
'U',
'00401',
'1',
'1',
'P',
'>'
]),
gs: new core_1.X12Segment('GS').setElements(['FA', 'TEST1', 'TEST2', '20200731', '0430', '1', 'X', '004010'])
}
})
try {
validator.assert(interchange, rule)
} catch (error) {
const { report } = error
assert.strictEqual(typeof report, 'object')
}
const acknowledgement = validator.acknowledge()
assert.strictEqual(acknowledgement instanceof core_1.X12Interchange, true)
should resolve error codes.
const errorTypes = ['element', 'segment', 'transaction', 'group']
const ackCodes = 'AMPRWXE'
for (const errorType of errorTypes) {
for (let i = 1, j = 1; i <= j; i += 1) {
const result = core_1.errorLookup(errorType, j.toString())
assert.strictEqual(typeof result, 'object')
if (parseFloat(result.code) > i - 1) {
j += 1
}
}
}
for (const char of ackCodes) {
const result = core_1.X12ValidationErrorCode.acknowledgement('group', char)
assert.strictEqual(typeof result, 'object')
}