Skip to content

Commit

Permalink
feat: update ssn validation criteria based on USWDS recommendations
Browse files Browse the repository at this point in the history
  • Loading branch information
kalasgarov committed Nov 20, 2024
1 parent 4f8786c commit 129433a
Show file tree
Hide file tree
Showing 2 changed files with 67 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,15 @@ describe('SocialSecurityNumberPattern tests', () => {
};

const schema = createSSNSchema(data);
const validInput = '555-11-0000';
const validInput = '555-11-1234';
const invalidInput = '444-44-56as';

expect(schema.safeParse(validInput).success).toBe(true);

const invalidResult = schema.safeParse(invalidInput);
expect(invalidResult.success).toBe(false);
expect(invalidResult.error?.issues[0].message).toBe(
'Social Security Number must contain exactly 9 digits and be formatted as XXX-XX-XXXX or XXXXXXXXX'
'Social Security Number must contain exactly 9 digits, be formatted as XXX-XX-XXXX or XXXXXXXXX, and meet SSA issuance criteria'
);
});

Expand All @@ -32,7 +33,7 @@ describe('SocialSecurityNumberPattern tests', () => {
};

const schema = createSSNSchema(data);
const validInput = '555-11-0000';
const validInput = '555-11-1234';
const emptyInput = '';
const invalidInput = '444-44-56as';

Expand All @@ -42,7 +43,7 @@ describe('SocialSecurityNumberPattern tests', () => {
const invalidResult = schema.safeParse(invalidInput);
expect(invalidResult.success).toBe(false);
expect(invalidResult.error?.issues[0].message).toBe(
'Social Security Number must contain exactly 9 digits and be formatted as XXX-XX-XXXX or XXXXXXXXX'
'Social Security Number must contain exactly 9 digits, be formatted as XXX-XX-XXXX or XXXXXXXXX, and meet SSA issuance criteria'
);
});

Expand All @@ -58,9 +59,45 @@ describe('SocialSecurityNumberPattern tests', () => {
const shortInputResult = schema.safeParse(shortInput);
expect(shortInputResult.success).toBe(false);
expect(shortInputResult.error?.issues[0].message).toBe(
'Social Security Number must contain exactly 9 digits and be formatted as XXX-XX-XXXX or XXXXXXXXX'
'Social Security Number must contain exactly 9 digits, be formatted as XXX-XX-XXXX or XXXXXXXXX, and meet SSA issuance criteria'
);
});

it('should fail with invalid SSN prefixes', () => {
const data: SocialSecurityNumberPattern['data'] = {
label: 'Test SSN Input Label',
required: true,
};

const schema = createSSNSchema(data);
const invalidSSNs = ['966-45-6789', '666-45-6789', '000-12-3456'];

invalidSSNs.forEach(ssn => {
const result = schema.safeParse(ssn);
expect(result.success).toBe(false);
expect(result.error?.issues[0].message).toBe(
'Social Security Number must contain exactly 9 digits, be formatted as XXX-XX-XXXX or XXXXXXXXX, and meet SSA issuance criteria'
);
});
});

it('should fail with invalid middle and suffix digits', () => {
const data: SocialSecurityNumberPattern['data'] = {
label: 'Test SSN Input Label',
required: true,
};

const schema = createSSNSchema(data);
const invalidSSNs = ['555-00-6789', '555-12-0000'];

invalidSSNs.forEach(ssn => {
const result = schema.safeParse(ssn);
expect(result.success).toBe(false);
expect(result.error?.issues[0].message).toBe(
'Social Security Number must contain exactly 9 digits, be formatted as XXX-XX-XXXX or XXXXXXXXX, and meet SSA issuance criteria'
);
});
});
});

describe('socialSecurityNumberConfig', () => {
Expand All @@ -74,7 +111,7 @@ describe('SocialSecurityNumberPattern tests', () => {
},
};

const inputValue = '555-11-0000';
const inputValue = '555-11-1234';
if (!socialSecurityNumberConfig.parseUserInput) {
expect.fail('socialSecurityNumberConfig.parseUserInput is undefined');
}
Expand Down Expand Up @@ -110,7 +147,7 @@ describe('SocialSecurityNumberPattern tests', () => {
if (!result.success) {
expect(result.error).toBeDefined();
expect(result.error?.message).toContain(
'Social Security Number must contain exactly 9 digits and be formatted as XXX-XX-XXXX or XXXXXXXXX'
'Social Security Number must contain exactly 9 digits, be formatted as XXX-XX-XXXX or XXXXXXXXX, and meet SSA issuance criteria'
);
} else {
expect.fail('Unexpected validation success');
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
import * as z from 'zod';

import { type SocialSecurityNumberProps } from '../../components.js';
import {
type Pattern,
type PatternConfig,
validatePattern,
} from '../../pattern.js';
import { type Pattern, type PatternConfig } from '../../pattern.js';
import { getFormSessionValue } from '../../session.js';
import {
safeZodParseFormErrors,
Expand All @@ -26,10 +21,29 @@ export type SocialSecurityNumberPatternOutput = z.infer<

export const createSSNSchema = (data: SocialSecurityNumberPattern['data']) => {
const ssnSchema = z.string().refine(value => {
const isValidFormat = /^\d{3}-\d{2}-\d{4}$|^\d{9}$/.test(value);
const stripped = value.replace(/[^0-9]/g, '');
return isValidFormat && stripped.length === 9;
}, 'Social Security Number must contain exactly 9 digits and be formatted as XXX-XX-XXXX or XXXXXXXXX');

const isValidFormat = /^\d{3}-\d{2}-\d{4}$|^\d{9}$/.test(value);

if (!isValidFormat) {
return false;
}

if (stripped.length !== 9) {
return false;
}

const hasValidPrefix = !(
stripped.startsWith('9') ||
stripped.startsWith('666') ||
stripped.startsWith('000')
);

const hasValidMiddle = stripped.slice(3, 5) !== '00';
const hasValidSuffix = stripped.slice(5) !== '0000';

return hasValidPrefix && hasValidMiddle && hasValidSuffix;
}, 'Social Security Number must contain exactly 9 digits, be formatted as XXX-XX-XXXX or XXXXXXXXX, and meet SSA issuance criteria');

if (!data.required) {
return ssnSchema.or(z.literal('').optional()).optional();
Expand Down Expand Up @@ -70,19 +84,6 @@ export const socialSecurityNumberConfig: PatternConfig<
const sessionValue = getFormSessionValue(session, pattern.id);
const error = session.data.errors[pattern.id];

/*
if (options.validate) {
const isValidResult = validatePattern(
socialSecurityNumberConfig,
pattern,
sessionValue
);
if (!isValidResult.success) {
extraAttributes['error'] = isValidResult.error;
}
}
*/

return {
props: {
_patternId: pattern.id,
Expand Down

0 comments on commit 129433a

Please sign in to comment.