diff --git a/src/parse/__tests__/td1.test.ts b/src/parse/__tests__/td1.test.ts index 03e58f1..cec6f47 100644 --- a/src/parse/__tests__/td1.test.ts +++ b/src/parse/__tests__/td1.test.ts @@ -1,5 +1,6 @@ import { describe, expect, it } from 'vitest'; +import { computeCheckDigit } from '../../parsers/check.ts'; import parse from '../parse.ts'; describe('parse TD1', () => { @@ -336,7 +337,7 @@ describe('parse TD1', () => { }); }); - it('parse document number', () => { + it('Document number field details', () => { const MRZ = [ 'I { documentNumber: result.fields.documentNumber, }); - expect(result.details.filter((f) => !f.valid)).toHaveLength(2); + expect(result.details.filter((item) => !item.valid)).toHaveLength(2); const documentNumberDetails = result.details.find( (d) => d.field === 'documentNumber', @@ -550,5 +551,112 @@ describe('parse TD1', () => { lastName: 'REINARTZ', firstName: 'ULRIKE KATIA E', }); + + const detailedCheckDigit = result.details.find( + (item) => item.field === 'documentNumberCheckDigit', + ); + expect(detailedCheckDigit).toMatchObject({ + valid: true, + start: 18, + end: 19, + value: '3', + }); + }); + it('Belgium ID BEL-BO-03003, with wrong document number check digit', () => { + // source: https://www.consilium.europa.eu/prado/en/BEL-BO-03003/index.html + // This Belgian ID has the document number check digit embedded in optional1 + const MRZ = [ + 'IDBEL000590240<6016<<<<<<<<<<<', + '8512017F1311048BEL851201002007', + 'REINARTZ< !item.valid); + expect(wrongFields).toHaveLength(2); + + const documentNumberDetails = wrongFields.find( + (item) => item.field === 'documentNumberCheckDigit', + ); + + expect(documentNumberDetails).toStrictEqual({ + valid: false, + start: 18, + end: 19, + value: '6', + field: 'documentNumberCheckDigit', + label: 'Document number check digit', + line: 0, + ranges: [ + { + line: 0, + start: 14, + end: 15, + raw: '<', + }, + { + line: 0, + start: 5, + end: 14, + raw: '000590240', + }, + { + line: 0, + start: 15, + end: 30, + raw: '6016<<<<<<<<<<<', + }, + ], + autocorrect: [], + error: `invalid check digit: 6. Must be ${expectedCheckDigit}`, + }); + + const compositeCheckDigitDetails = wrongFields.find( + (item) => item.field === 'compositeCheckDigit', + ); + + expect(compositeCheckDigitDetails).toBeDefined(); + }); + + it('Belgium ID BEL-BO-03003, with wrong birth date check digit', () => { + // source: https://www.consilium.europa.eu/prado/en/BEL-BO-03003/index.html + // The birthdate check digit was changed + const MRZ = [ + 'IDBEL000590240<6013<<<<<<<<<<<', + '8512018F1311048BEL851201002007', + 'REINARTZ< !item.valid); + expect(wrongDetails).toHaveLength(2); + + const birthDateDetails = wrongDetails.find( + (item) => item.field === 'birthDateCheckDigit', + ); + + expect(birthDateDetails).toMatchObject({ + valid: false, + start: 6, + end: 7, + value: '8', + error: `invalid check digit: 8. Must be 7`, + }); }); }); diff --git a/src/parse/__tests__/td3.test.ts b/src/parse/__tests__/td3.test.ts index 1b8f018..e5ff434 100644 --- a/src/parse/__tests__/td3.test.ts +++ b/src/parse/__tests__/td3.test.ts @@ -37,6 +37,7 @@ describe('parse TD3', () => { const errors = result.details.filter((a) => !a.valid); + // Issuing state and nationality expect(errors).toHaveLength(2); const personalNumberDetails = result.details.find( @@ -69,6 +70,43 @@ describe('parse TD3', () => { }); }); + it('Utopia example - wrong personal number check digit', () => { + // The same example as the previous one, but the 2nd character from the end was changed from a '1' to a '2'. + const MRZ = [ + 'P !item.valid); + // Issuing state, nationality, personal number check digit and composite check digit + expect(wrongDetails).toHaveLength(4); + expect( + wrongDetails.find((item) => item.field === 'personalNumberCheckDigit'), + ).toMatchObject({ + start: 42, + end: 43, + value: '2', + error: 'invalid check digit: 2. Must be 1', + }); + + expect( + wrongDetails.find((item) => item.field === 'compositeCheckDigit'), + ).toMatchObject({ + start: 43, + end: 44, + value: '0', + error: 'invalid check digit: 0. Must be 1', + }); + }); + it('German example', () => { const MRZ = [ 'P ParseResult | string; @@ -96,11 +98,22 @@ export default function createFieldParser( result.end = range.end; try { const parsed = fieldOptions.parser(source, ...textRelated); - result.value = typeof parsed === 'object' ? parsed.value : parsed; - result.valid = true; + if (typeof parsed === 'object') { - result.start = range.start + parsed.start; - result.end = range.start + parsed.end; + result.value = parsed.value; + result.valid = parsed.valid ?? true; + if (parsed.start !== undefined) { + result.start = range.start + parsed.start; + } + if (parsed.end !== undefined) { + result.end = range.start + parsed.end; + } + if (parsed.valid === false && parsed.error) { + result.error = parsed.error; + } + } else { + result.value = parsed; + result.valid = true; } } catch (error) { result.error = error.message; diff --git a/src/parsers/__tests__/check.test.ts b/src/parsers/__tests__/check.test.ts index 82fcf66..364d8dd 100644 --- a/src/parsers/__tests__/check.test.ts +++ b/src/parsers/__tests__/check.test.ts @@ -3,16 +3,62 @@ import { expect, test } from 'vitest'; import { check, computeCheckDigit } from '../check.ts'; test('check digits', () => { - expect(() => check('592166117<231', 8)).not.toThrow(); - expect(() => check('592166111<773', 5)).not.toThrow(); - expect(() => check('007666667 check('007666667ZZ0', 0)).not.toThrow(); - expect(() => check('007777779ZZ9', 2)).not.toThrow(); - expect(() => check('600001795015', 2)).not.toThrow(); - expect(() => check('592166111<773', 4)).toThrow(/invalid check digit/); + expect(check('592166117<231', '8')).toStrictEqual({ + valid: true, + error: null, + }); + expect(check('592166111<773', '5')).toStrictEqual({ + valid: true, + error: null, + }); + expect(check('007666667 { - expect(computeCheckDigit('123456789AAB')).toBe(8); - expect(computeCheckDigit('123456789 { + // https://www.consilium.europa.eu/prado/en/PRT-BO-04001/index.html + // I { + // https://www.consilium.europa.eu/prado/en/BEL-BO-03003/index.html + // IDBEL000590240<6013<<<<<<<<<<< + expect(computeCheckDigit('000590240<601')).toBe(3); + expect(computeCheckDigit('000590240601')).not.toBe(3); +}); + +test('compute embedded TD1 check digit - must not include < character', () => { + // https://www.consilium.europa.eu/prado/en/BEL-BO-11005/index.html + // IDBEL600001795<0152<<<<<<<<<<< + expect(computeCheckDigit('600001795015')).toBe(2); + expect(computeCheckDigit('600001795<015')).not.toBe(2); +}); + +test('embedded check digit - ICAO', () => { + // https://www.icao.int/sites/default/files/publications/DocSeries/9303_p11_cons_en.pdf + // Page 88 + // I