Skip to content

Commit

Permalink
Added control digit validation.
Browse files Browse the repository at this point in the history
  • Loading branch information
Martin Severin Steffensen committed Jul 19, 2021
1 parent 27e5037 commit d809268
Show file tree
Hide file tree
Showing 9 changed files with 78 additions and 14 deletions.
3 changes: 2 additions & 1 deletion src/ValidationErrorCode.ts
Original file line number Diff line number Diff line change
@@ -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.
}
4 changes: 3 additions & 1 deletion src/getDateOfBirthFromSsn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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`)
}
}

export const validateDateOfBirth = (dateOfBirth: Date): boolean => dateOfBirth instanceof Date && !isNaN(dateOfBirth.getTime())
33 changes: 33 additions & 0 deletions src/validateControlDigits.ts
Original file line number Diff line number Diff line change
@@ -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<number> => {
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]
}
9 changes: 5 additions & 4 deletions src/validateSsn.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -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()
}
2 changes: 1 addition & 1 deletion tests/getDateOfBirthFromSsn.spec.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,16 @@ export {}
declare global {
namespace jest {
interface Matchers<R> {
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}`
}
}
})
27 changes: 27 additions & 0 deletions tests/validateControlDigits.ts
Original file line number Diff line number Diff line change
@@ -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)
})
})
4 changes: 2 additions & 2 deletions tests/validateIndividualDigits.spec.ts
Original file line number Diff line number Diff line change
@@ -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)
Expand Down Expand Up @@ -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}]`)
}
}

Expand Down
4 changes: 2 additions & 2 deletions tests/validateSsn.spec.ts
Original file line number Diff line number Diff line change
@@ -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)
Expand Down

0 comments on commit d809268

Please sign in to comment.