diff --git a/src/ValidationErrorCode.ts b/src/ValidationErrorCode.ts index 6b6c9e1..85420e4 100644 --- a/src/ValidationErrorCode.ts +++ b/src/ValidationErrorCode.ts @@ -1,5 +1,6 @@ export enum ValidationErrorCode { InvalidBirthDate, // Birth date is not a valid date. InvalidIndividualDigits, // Birth date does not match individual digits. - InvalidSsnFormat // Provided SSN does not conform to the expected format. + InvalidSsnFormat, // Provided SSN does not conform to the expected format. + InvalidControlDigits // Control digits do not match birth date and individual digits. } \ No newline at end of file diff --git a/src/getDateOfBirthFromSsn.ts b/src/getDateOfBirthFromSsn.ts index 0772489..64c7ecd 100644 --- a/src/getDateOfBirthFromSsn.ts +++ b/src/getDateOfBirthFromSsn.ts @@ -5,4 +5,6 @@ export const getDateOfBirthFromSsn = (value: string): Date => { yearOfBirth += yearOfBirth > 50 ? 1900 : 2000 return new Date(`${yearOfBirth}-${monthOfBirth}-${dayOfBirth}T00:00:00.000Z`) -} \ No newline at end of file +} + +export const validateDateOfBirth = (dateOfBirth: Date): boolean => dateOfBirth instanceof Date && !isNaN(dateOfBirth.getTime()) \ No newline at end of file diff --git a/src/validateControlDigits.ts b/src/validateControlDigits.ts new file mode 100644 index 0000000..dae5548 --- /dev/null +++ b/src/validateControlDigits.ts @@ -0,0 +1,33 @@ +const getFirstControlDigit = (d1: number, d2: number, m1: number, m2: number, y1: number, y2: number, i1: number, i2: number, i3: number): number => { + const firstControlDigit = 11 - ((3 * d1 + 7 * d2 + 6 * m1 + 1 * m2 + 8 * y1 + 9 * y2 + 4 * i1 + 5 * i2 + 2 * i3) % 11) + return firstControlDigit === 11 ? 0 : firstControlDigit +} + +const getSecondControlDigit = (d1: number, d2: number, m1: number, m2: number, y1: number, y2: number, i1: number, i2: number, i3: number, c1: number): number => { + const secondControlDigit = 11 - ((5 * d1 + 4 * d2 + 3 * m1 + 2 * m2 + 7 * y1 + 6 * y2 + 5 * i1 + 4 * i2 + 3 * i3 + 2 * c1) % 11) + return secondControlDigit === 11 ? 0 : secondControlDigit +} + +const getControlDigitsFromSsn = (ssn: string): Array => { + const d1 = Number(ssn.slice(0, 1)) + const d2 = Number(ssn.slice(1, 2)) + const m1 = Number(ssn.slice(2, 3)) + const m2 = Number(ssn.slice(3, 4)) + const y1 = Number(ssn.slice(4, 5)) + const y2 = Number(ssn.slice(5, 6)) + const i1 = Number(ssn.slice(6, 7)) + const i2 = Number(ssn.slice(7, 8)) + const i3 = Number(ssn.slice(8, 9)) + const c1 = getFirstControlDigit(d1, d2, m1, m2, y1, y2, i1, i2, i3) + const c2 = getSecondControlDigit(d1, d2, m1, m2, y1, y2, i1, i2, i3, c1) + + return [c1, c2] +} + +export const validateControlDigits = (ssn: string): boolean => { + const firstControlDigit = Number(ssn.slice(9, 10)) + const secondControlDigit = Number(ssn.slice(10, 11)) + const actualControlDigits = getControlDigitsFromSsn(ssn) + + return firstControlDigit === actualControlDigits[0] && secondControlDigit === actualControlDigits[1] +} \ No newline at end of file diff --git a/src/validateSsn.ts b/src/validateSsn.ts index e7a8d72..eba82cd 100644 --- a/src/validateSsn.ts +++ b/src/validateSsn.ts @@ -1,4 +1,5 @@ -import { getDateOfBirthFromSsn } from './getDateOfBirthFromSsn' +import { getDateOfBirthFromSsn, validateDateOfBirth } from './getDateOfBirthFromSsn' +import { validateControlDigits } from './validateControlDigits' import { validateIndividualDigits } from './validateIndividualDigits' import { ValidationErrorCode } from './ValidationErrorCode' import { ValidationResult } from './ValidationResult' @@ -7,11 +8,11 @@ export const validateSsn = (value: string): ValidationResult => { if(!/\d{11}/.test(value)) return new ValidationResult(ValidationErrorCode.InvalidSsnFormat) const dateOfBirth = getDateOfBirthFromSsn(value) - if(!(dateOfBirth instanceof Date) || isNaN(dateOfBirth.getTime())) return new ValidationResult(ValidationErrorCode.InvalidBirthDate) - const individualDigits = Number(value.slice(6, 9)) - if(!validateIndividualDigits(individualDigits, dateOfBirth)) return new ValidationResult(ValidationErrorCode.InvalidIndividualDigits) + if(!validateDateOfBirth(dateOfBirth)) return new ValidationResult(ValidationErrorCode.InvalidBirthDate) + if(!validateIndividualDigits(individualDigits, dateOfBirth)) return new ValidationResult(ValidationErrorCode.InvalidIndividualDigits) + if(!validateControlDigits(value)) return new ValidationResult(ValidationErrorCode.InvalidControlDigits) return new ValidationResult() } \ No newline at end of file diff --git a/tests/getDateOfBirthFromSsn.spec.ts b/tests/getDateOfBirthFromSsn.spec.ts index c2560f1..9261b0c 100644 --- a/tests/getDateOfBirthFromSsn.spec.ts +++ b/tests/getDateOfBirthFromSsn.spec.ts @@ -1,6 +1,6 @@ import { getDateOfBirthFromSsn } from '../src/getDateOfBirthFromSsn' -describe('it should suceed', () => { +describe('it should succeed', () => { test('when the date of birth is valid', () => { // arrange const ssn = '01015111111' diff --git a/tests/toBeExpectedBoolean.ts b/tests/toBeWithErrorMessage.ts similarity index 50% rename from tests/toBeExpectedBoolean.ts rename to tests/toBeWithErrorMessage.ts index 89c0865..ea82421 100644 --- a/tests/toBeExpectedBoolean.ts +++ b/tests/toBeWithErrorMessage.ts @@ -3,16 +3,16 @@ export {} declare global { namespace jest { interface Matchers { - toBeExpectedBoolean(expected: boolean, context: string): CustomMatcherResult + toBeWithErrorMessage(expected: boolean, errorMessage: string): CustomMatcherResult } } } expect.extend({ - toBeExpectedBoolean(received: boolean, expected: boolean, context: string) { + toBeWithErrorMessage(received: boolean, expected: boolean, errorMessage: string) { return { pass: received === expected, - message: () => `Expected values to be equal, but they weren't. Received: ${received}. Context: ${context}` + message: () => `Expected values to be equal, but they weren't. Received: ${received}. Error message: ${errorMessage}` } } }) \ No newline at end of file diff --git a/tests/validateControlDigits.ts b/tests/validateControlDigits.ts new file mode 100644 index 0000000..af2c7c0 --- /dev/null +++ b/tests/validateControlDigits.ts @@ -0,0 +1,27 @@ +import { validateControlDigits } from '../src/getControlDigitsFromSsn' + +describe('it should succeed', () => { + test('when the control digits are valid', () => { + // arrange + const ssn = '01015111190' + + // act + const result = validateControlDigits(ssn) + + // assert + expect(result).toBe(true) + }) +}) + +describe('it should fail', () => { + test('when the control digits are invalid', () => { + // arrange + const ssn = '01015111111' + + // act + const result = validateControlDigits(ssn) + + // assert + expect(result).toBe(false) + }) +}) \ No newline at end of file diff --git a/tests/validateIndividualDigits.spec.ts b/tests/validateIndividualDigits.spec.ts index f3db164..ee5f5a5 100644 --- a/tests/validateIndividualDigits.spec.ts +++ b/tests/validateIndividualDigits.spec.ts @@ -1,5 +1,5 @@ import { validateIndividualDigits } from '../src/validateIndividualDigits' -import './toBeExpectedBoolean' +import './toBeWithErrorMessage' const IS_VERBOSE = Boolean(process.env.TEST_VALIDATE_INDIVIDUAL_DIGITS_VERBOSE || false) const ITERATIONS = Number(process.env.TEST_VALIDATE_INDIVIDUAL_DIGITS_ITERATIONS || 1000) @@ -29,7 +29,7 @@ const ITERATIONS = Number(process.env.TEST_VALIDATE_INDIVIDUAL_DIGITS_ITERATIONS const result = validateIndividualDigits(individualDigits, dateofBirth) // assert - expect(result).toBeExpectedBoolean(shouldSucceed, `[individualDigits:${individualDigits}, dateOfBirth:${dateofBirth.toISOString()}, expected:${shouldSucceed}]`) + expect(result).toBeWithErrorMessage(shouldSucceed, `[individualDigits:${individualDigits}, dateOfBirth:${dateofBirth.toISOString()}, expected:${shouldSucceed}]`) } } diff --git a/tests/validateSsn.spec.ts b/tests/validateSsn.spec.ts index 37ba929..0871daa 100644 --- a/tests/validateSsn.spec.ts +++ b/tests/validateSsn.spec.ts @@ -1,11 +1,11 @@ import { validateSsn } from '../src/validateSsn' import { ValidationErrorCode } from '../src/ValidationErrorCode' -import './toBeExpectedBoolean' +import './toBeWithErrorMessage' describe('it should succeed', () => { test('when the ssn is valid', () => { // arrange - const ssn = '01015111111' + const ssn = '01015111190' // act const result = validateSsn(ssn)