UNPKG

meta-capi-param-builder-clientjs

Version:

Conversions API parameter builder for Client-side JavaScript

1,027 lines (944 loc) 27.9 kB
/* * Copyright (c) Meta Platforms, Inc. and affiliates. * All rights reserved. * * This source code is licensed under the license found in the * LICENSE file in the root directory of this source tree. */ import { getNormalizedPII } from '../utils/piiUtil/piiUtil.js'; import { getNormalizedState } from '../utils/piiUtil/stringUtil.js'; import { PII_DATA_TYPE } from '../model/constants.js'; describe('getNormalizedState', () => { describe('Input validation', () => { it('should return null for null input', () => { expect(getNormalizedState(null)).toBeNull(); }); it('should return null for undefined input', () => { expect(getNormalizedState(undefined)).toBeNull(); }); it('should convert non-string inputs to strings and normalize', () => { const testCases = [ { input: 123, expected: null, // Starts with number, fails regex test '^[a-z]+' }, { input: true, expected: 'tr', // 'true' becomes 'tr' after truncate, passes regex test }, { input: false, expected: 'fa', // 'false' becomes 'fa' after truncate, passes regex test }, { input: {}, expected: 'ob', // '[object Object]' becomes 'objectobject', truncated to 'ob' }, { input: [], expected: null, // Empty array toString is empty string, fails regex test }, ]; testCases.forEach(({ input, expected }) => { expect(getNormalizedState(input)).toEqual(expected); }); }); it('should handle empty string input', () => { expect(getNormalizedState('')).toBeNull(); // Empty string fails regex test '^[a-z]+' }); it('should handle whitespace-only input', () => { expect(getNormalizedState(' ')).toBeNull(); // Whitespace stripped to empty, fails regex test expect(getNormalizedState('\t\n\r')).toBeNull(); // Whitespace stripped to empty, fails regex test }); }); describe('State normalization', () => { it('should normalize example data', () => { const testCases = [ { input: ' California. ', expected: 'ca', }, { input: 'CA', expected: 'ca', }, { input: ' TE', expected: 'te', }, { input: ' C/a/lifo,rnia. ', expected: 'ca', }, ]; testCases.forEach(({ input, expected }) => { expect(getNormalizedState(input)).toEqual(expected); }); }); it('should normalize US state names to abbreviations', () => { const testCases = [ { input: 'Alabama', expected: 'al', }, { input: 'Alaska', expected: 'ak', }, { input: 'Arizona', expected: 'az', }, { input: 'Arkansas', expected: 'ar', }, { input: 'California', expected: 'ca', }, { input: 'Colorado', expected: 'co', }, { input: 'Connecticut', expected: 'ct', }, { input: 'Delaware', expected: 'de', }, { input: 'Florida', expected: 'fl', }, { input: 'Georgia', expected: 'ga', }, { input: 'Hawaii', expected: 'hi', }, { input: 'Idaho', expected: 'id', }, { input: 'Illinois', expected: 'il', }, { input: 'Indiana', expected: 'in', }, { input: 'Iowa', expected: 'ia', }, { input: 'Kansas', expected: 'ks', }, { input: 'Kentucky', expected: 'ky', }, { input: 'Louisiana', expected: 'la', }, { input: 'Maine', expected: 'me', }, { input: 'Maryland', expected: 'md', }, { input: 'Massachusetts', expected: 'ma', }, { input: 'Michigan', expected: 'mi', }, { input: 'Minnesota', expected: 'mn', }, { input: 'Mississippi', expected: 'ms', }, { input: 'Missouri', expected: 'mo', }, { input: 'Montana', expected: 'mt', }, { input: 'Nebraska', expected: 'ne', }, { input: 'Nevada', expected: 'nv', }, { input: 'New Hampshire', expected: 'nh', }, { input: 'New Jersey', expected: 'nj', }, { input: 'New Mexico', expected: 'nm', }, { input: 'New York', expected: 'ny', }, { input: 'North Carolina', expected: 'nc', }, { input: 'North Dakota', expected: 'nd', }, { input: 'Ohio', expected: 'oh', }, { input: 'Oklahoma', expected: 'ok', }, { input: 'Oregon', expected: 'or', }, { input: 'Pennsylvania', expected: 'pa', }, { input: 'Rhode Island', expected: 'ri', }, { input: 'South Carolina', expected: 'sc', }, { input: 'South Dakota', expected: 'sd', }, { input: 'Tennessee', expected: 'tn', }, { input: 'Texas', expected: 'tx', }, { input: 'Utah', expected: 'ut', }, { input: 'Vermont', expected: 'vt', }, { input: 'Virginia', expected: 'va', }, { input: 'Washington', expected: 'wa', }, { input: 'West Virginia', expected: 'wv', }, { input: 'Wisconsin', expected: 'wi', }, { input: 'Wyoming', expected: 'wy', }, ]; testCases.forEach(({ input, expected }) => { expect(getNormalizedState(input)).toEqual(expected); }); }); it('should normalize Canadian province names to abbreviations', () => { const testCases = [ { input: 'Ontario', expected: 'on', }, { input: 'Quebec', expected: 'qc', }, { input: 'British Columbia', expected: 'bc', }, { input: 'Alberta', expected: 'ab', }, { input: 'Saskatchewan', expected: 'sk', }, { input: 'Manitoba', expected: 'mb', }, { input: 'Nova Scotia', expected: 'ns', }, { input: 'New Brunswick', expected: 'nb', }, { input: 'Prince Edward Island', expected: 'pe', }, { input: 'Newfoundland and Labrador', expected: 'nl', }, { input: 'Yukon', expected: 'yt', }, { input: 'Northwest Territories', expected: 'nt', }, { input: 'Nunavut', expected: 'nu', }, ]; testCases.forEach(({ input, expected }) => { expect(getNormalizedState(input)).toEqual(expected); }); }); it('should handle existing abbreviations', () => { const testCases = [ { input: 'CA', expected: 'ca', }, { input: 'NY', expected: 'ny', }, { input: 'TX', expected: 'tx', }, { input: 'FL', expected: 'fl', }, { input: 'ON', expected: 'on', }, { input: 'BC', expected: 'bc', }, ]; testCases.forEach(({ input, expected }) => { expect(getNormalizedState(input)).toEqual(expected); }); }); it('should handle mixed case state names', () => { const testCases = [ { input: 'CaLiFoRnIa', expected: 'ca', }, { input: 'nEw YoRk', expected: 'ny', }, { input: 'tExAs', expected: 'tx', }, { input: 'ca', expected: 'ca', }, { input: 'CA', expected: 'ca', }, ]; testCases.forEach(({ input, expected }) => { expect(getNormalizedState(input)).toEqual(expected); }); }); it('should handle states with leading/trailing whitespace', () => { const testCases = [ { input: ' California ', expected: 'ca', }, { input: '\tNew York\n', expected: 'ny', }, { input: ' Texas ', expected: 'tx', }, { input: '\r\nFlorida\t', expected: 'fl', }, ]; testCases.forEach(({ input, expected }) => { expect(getNormalizedState(input)).toEqual(expected); }); }); it('should handle states with punctuation and special characters', () => { const testCases = [ { input: 'California!', expected: 'ca', }, { input: 'New-York', expected: 'ny', }, { input: 'Texas.', expected: 'tx', }, { input: 'Florida@#$', expected: 'fl', }, { input: 'CA-1', expected: 'ca', }, ]; testCases.forEach(({ input, expected }) => { expect(getNormalizedState(input)).toEqual(expected); }); }); it('should handle partial matches within longer strings', () => { const testCases = [ { input: 'CaliforniaState', expected: 'ca', }, { input: 'NewYorkCity', expected: 'ny', }, { input: 'TexasCounty', expected: 'tx', }, { input: 'FloridaKeys', expected: 'fl', }, ]; testCases.forEach(({ input, expected }) => { expect(getNormalizedState(input)).toEqual(expected); }); }); it('should truncate to 2 characters for non-mapped states', () => { const testCases = [ { input: 'Unknown', expected: 'un', }, { input: 'InvalidState', expected: 'in', }, { input: 'TestState', expected: 'te', }, { input: 'AB', expected: 'ab', // Already 2 chars }, { input: 'X', expected: 'x', // Single char returned as-is }, ]; testCases.forEach(({ input, expected }) => { expect(getNormalizedState(input)).toEqual(expected); }); }); it('should handle states that contain known state names even with numbers', () => { const testCases = [ { input: '1California', expected: 'ca', // Finds "california" within the string }, { input: '2ndState', expected: 'nd', // Finds "nd" (North Dakota abbreviation) within the string }, { input: '3rdPlace', expected: 'rd', // Finds "rd" (abbreviation pattern) }, { input: '99Problems', expected: 'pr', // No state match, truncates to first 2 characters after processing }, ]; testCases.forEach(({ input, expected }) => { expect(getNormalizedState(input)).toEqual(expected); }); }); it('should handle single character inputs', () => { const testCases = [ { input: 'A', expected: 'a', }, { input: 'B', expected: 'b', }, { input: 'C', expected: 'c', }, { input: '1', expected: null, // Starts with number, fails regex test }, ]; testCases.forEach(({ input, expected }) => { expect(getNormalizedState(input)).toEqual(expected); }); }); it('should return null for empty input after stripping', () => { const testCases = [ '123', // All numbers, stripped to empty '!@#$%', // All punctuation, stripped to empty ' ', // All whitespace, stripped to empty '', // Already empty ]; testCases.forEach((input) => { expect(getNormalizedState(input)).toBeNull(); }); }); }); describe('Hashed state handling', () => { it('should return SHA-256 hash as-is when it looks like a hash', () => { const validHashes = [ 'a665a45920422f9d417e4867efdc4fb8a04a1f3fff1fa07e998e86f7f7a27ae3', // 'hello' hashed 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', // empty string hashed '2cf24dba4f21d4288094e8452703c0f0142fa00b2eeb1f2c9b4e70f39e8a4c29', // 'hello' in different case 'ABCDEF1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF1234567890', // uppercase hash '1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef', // mixed case hash ]; validHashes.forEach((hash) => { const result = getNormalizedState(hash); expect(result).toEqual(hash); }); }); it('should process invalid hash-like strings as regular state names', () => { const invalidHashes = [ { input: 'a665a45920422f9d417e4867efdc4fb8a04a1f3fff1fa07e998e86f7f7a27ae', // 63 chars expected: 'aa', // Numbers stripped, truncated to first 2 alphabetic chars }, { input: 'a665a45920422f9d417e4867efdc4fb8a04a1f3fff1fa07e998e86f7f7a27ae33', // 65 chars expected: 'aa', // Numbers stripped, truncated to first 2 alphabetic chars }, { input: 'g665a45920422f9d417e4867efdc4fb8a04a1f3fff1fa07e998e86f7f7a27ae3', // invalid char 'g' expected: 'ga', // Finds "georgia" substring and returns abbreviation }, { input: 'A665A45920422F9D417E4867EFDC4FB8A04A1F3FFF1FA07E998E86F7F7A27AE!', // invalid char '!' expected: 'aa', // ! stripped, numbers stripped, lowercased and truncated to first 2 alphabetic chars }, { input: '123', // too short for hash, starts with number expected: null, // Fails regex test '^[a-z]+' }, ]; invalidHashes.forEach(({ input, expected }) => { const result = getNormalizedState(input); expect(result).toEqual(expected); }); }); it('should handle mixed case hashed states', () => { const mixedCaseHash = 'A665A45920422F9D417E4867EFDC4FB8A04A1F3FFF1FA07E998E86F7F7A27AE3'; const result = getNormalizedState(mixedCaseHash); expect(result).toEqual(mixedCaseHash); }); it('should handle lowercase hashed states', () => { const lowercaseHash = 'a665a45920422f9d417e4867efdc4fb8a04a1f3fff1fa07e998e86f7f7a27ae3'; const result = getNormalizedState(lowercaseHash); expect(result).toEqual(lowercaseHash); }); }); describe('Edge cases', () => { it('should handle numeric strings starting with numbers', () => { const testCases = [ { input: '123', expected: null, // All numbers, fails regex test }, { input: '0', expected: null, // Single number, fails regex test }, { input: '456State', expected: 'st', // Finds 'st' after processing numbers }, { input: '789Province', expected: 'pr', // Finds 'pr' after processing numbers }, ]; testCases.forEach(({ input, expected }) => { expect(getNormalizedState(input)).toEqual(expected); }); }); it('should handle extreme cases', () => { const testCases = [ { input: String(Number.MAX_SAFE_INTEGER), expected: null, // Starts with number, fails regex test }, { input: 'California'.repeat(100), // Very long string expected: 'ca', // Should still find California mapping }, { input: 'A'.repeat(1000), // Very long string expected: 'aa', // Truncated to 2 characters }, { input: '!@#$%^&*()_+-=[]{}|;:,.<>?', expected: null, // All punctuation stripped to empty, fails regex test }, ]; testCases.forEach(({ input, expected }) => { expect(getNormalizedState(input)).toEqual(expected); }); }); it('should handle Unicode and international characters', () => { const testCases = [ { input: 'Califórnia', // With accent expected: 'ca', // Accent stripped, still matches California }, { input: 'Québec', // French province with accent expected: 'qu', // Accent stripped, becomes 'Qubec', truncated to 'qu' }, { input: 'Москва', // Russian characters expected: null, // Non-Latin characters stripped to empty }, { input: '北京', // Chinese characters expected: null, // Non-Latin characters stripped to empty }, ]; testCases.forEach(({ input, expected }) => { expect(getNormalizedState(input)).toEqual(expected); }); }); }); }); describe('getNormalizedPII with STATE', () => { describe('Input validation', () => { it('should return null for null input', () => { expect(getNormalizedPII(null, PII_DATA_TYPE.STATE)).toBeNull(); }); it('should return null for undefined input', () => { expect(getNormalizedPII(undefined, PII_DATA_TYPE.STATE)).toBeNull(); }); it('should return null for non-string input', () => { const nonStringValues = [123, {}, [], true, false]; nonStringValues.forEach((value) => { expect(getNormalizedPII(value, PII_DATA_TYPE.STATE)).toBeNull(); }); }); it('should return null for empty string input', () => { expect(getNormalizedPII('', PII_DATA_TYPE.STATE)).toBeNull(); }); it('should return null for whitespace-only input', () => { expect(getNormalizedPII(' ', PII_DATA_TYPE.STATE)).toBeNull(); expect(getNormalizedPII('\t\n\r', PII_DATA_TYPE.STATE)).toBeNull(); }); it('should return null for null data type', () => { expect(getNormalizedPII('California', null)).toBeNull(); }); it('should return null for undefined data type', () => { expect(getNormalizedPII('California', undefined)).toBeNull(); }); it('should return null for invalid data type', () => { expect(getNormalizedPII('California', 'invalid_type')).toBeNull(); }); }); describe('State normalization', () => { it('should normalize state names using getNormalizedState', () => { const testCases = [ { input: 'California', expected: 'ca', }, { input: 'NEW YORK', expected: 'ny', }, { input: ' Texas ', expected: 'tx', }, { input: 'Florida', expected: 'fl', }, { input: 'Ontario', expected: 'on', }, { input: 'British Columbia', expected: 'bc', }, ]; testCases.forEach(({ input, expected }) => { expect(getNormalizedPII(input, PII_DATA_TYPE.STATE)).toEqual(expected); }); }); it('should handle states containing numbers differently than pure number starts', () => { const testCases = [ { input: '1stState', expected: 'st', // After processing, truncates to 'st' }, { input: '2ndProvince', expected: 'nd', // Finds 'nd' (North Dakota abbreviation) within the string }, { input: '42ndState', expected: 'nd', // Finds 'nd' (North Dakota abbreviation) within the string }, ]; testCases.forEach(({ input, expected }) => { expect(getNormalizedPII(input, PII_DATA_TYPE.STATE)).toEqual(expected); }); }); it('should handle existing abbreviations', () => { const testCases = [ { input: 'CA', expected: 'ca', }, { input: 'NY', expected: 'ny', }, { input: 'TX', expected: 'tx', }, { input: 'ON', expected: 'on', }, ]; testCases.forEach(({ input, expected }) => { expect(getNormalizedPII(input, PII_DATA_TYPE.STATE)).toEqual(expected); }); }); it('should handle hashed state names', () => { const hash = 'a665a45920422f9d417e4867efdc4fb8a04a1f3fff1fa07e998e86f7f7a27ae3'; expect(getNormalizedPII(hash, PII_DATA_TYPE.STATE)).toEqual(hash); }); }); describe('Other PII data types', () => { it('should return null for invalid email when using EMAIL data type', () => { expect(getNormalizedPII('California', PII_DATA_TYPE.EMAIL)).toBeNull(); expect(getNormalizedPII('New York', PII_DATA_TYPE.EMAIL)).toBeNull(); }); it('should return normalized phone for state when using PHONE data type', () => { expect(getNormalizedPII('California', PII_DATA_TYPE.PHONE)).toEqual(''); expect(getNormalizedPII('State123', PII_DATA_TYPE.PHONE)).toEqual('123'); expect(getNormalizedPII('123-456-7890', PII_DATA_TYPE.PHONE)).toEqual( '1234567890' ); }); it('should return normalized DOB for state when using DATE_OF_BIRTH data type', () => { expect( getNormalizedPII('California', PII_DATA_TYPE.DATE_OF_BIRTH) ).toBeNull(); // Invalid date expect( getNormalizedPII('New York', PII_DATA_TYPE.DATE_OF_BIRTH) ).toBeNull(); // Invalid date expect( getNormalizedPII('12-31-1990', PII_DATA_TYPE.DATE_OF_BIRTH) ).toEqual('19901231'); }); it('should return normalized gender for state when using GENDER data type', () => { expect(getNormalizedPII('male', PII_DATA_TYPE.GENDER)).toEqual('m'); expect(getNormalizedPII('female', PII_DATA_TYPE.GENDER)).toEqual('f'); expect(getNormalizedPII('California', PII_DATA_TYPE.GENDER)).toBeNull(); // Invalid gender term }); it('should return normalized name for state when using NAME data types', () => { expect(getNormalizedPII('California', PII_DATA_TYPE.FIRST_NAME)).toEqual( 'california' ); expect(getNormalizedPII('New York', PII_DATA_TYPE.LAST_NAME)).toEqual( 'newyork' ); expect(getNormalizedPII('Texas', PII_DATA_TYPE.FIRST_NAME)).toEqual( 'texas' ); }); it('should return normalized city for state when using CITY data type', () => { expect(getNormalizedPII('California', PII_DATA_TYPE.CITY)).toEqual( 'california' ); expect(getNormalizedPII('New York', PII_DATA_TYPE.CITY)).toEqual( 'newyork' ); expect(getNormalizedPII('123California', PII_DATA_TYPE.CITY)).toBeNull(); // Starts with number }); it('should return null for truly unsupported data types', () => { const unsupportedTypes = ['invalid_type', 'unknown', 'fake_type']; unsupportedTypes.forEach((type) => { expect(getNormalizedPII('California', type)).toBeNull(); }); }); }); describe('Comparison with direct getNormalizedState', () => { it('should show both functions behave the same way for valid states', () => { const testCases = [ { input: 'California', expected: 'ca', }, { input: ' New York ', expected: 'ny', }, { input: 'Texas', expected: 'tx', }, { input: 'Ontario', expected: 'on', }, ]; testCases.forEach(({ input, expected }) => { expect(getNormalizedState(input)).toEqual(expected); expect(getNormalizedPII(input, PII_DATA_TYPE.STATE)).toEqual(expected); }); }); it('should show both handle invalid states the same way', () => { const testCases = [ { input: '1stState', expected: 'st', // Finds 'st' after processing }, { input: '北京', expected: null, // Non-Latin characters stripped to empty }, { input: '...', expected: null, // All punctuation stripped to empty }, ]; testCases.forEach(({ input, expected }) => { expect(getNormalizedState(input)).toEqual(expected); expect(getNormalizedPII(input, PII_DATA_TYPE.STATE)).toEqual(expected); }); }); it('should show both handle hashes the same way', () => { const hash = 'a665a45920422f9d417e4867efdc4fb8a04a1f3fff1fa07e998e86f7f7a27ae3'; expect(getNormalizedState(hash)).toEqual(hash); expect(getNormalizedPII(hash, PII_DATA_TYPE.STATE)).toEqual(hash); }); it('should show different handling for null/invalid inputs due to PII interface validation', () => { const invalidInputs = [null, undefined]; invalidInputs.forEach((input) => { // Direct function returns null for null/undefined expect(getNormalizedState(input)).toBeNull(); // PII interface also returns null for invalid inputs (validates input first) expect(getNormalizedPII(input, PII_DATA_TYPE.STATE)).toBeNull(); }); }); it('should show different handling for non-string inputs', () => { const nonStringInputs = [123, {}, [], true, false]; nonStringInputs.forEach((input) => { // Direct function converts non-strings to strings and processes them const directResult = getNormalizedState(input); expect( [null, 'string'].includes(typeof directResult) || directResult === null ).toBe(true); // PII interface returns null for non-string inputs (validates input first) expect(getNormalizedPII(input, PII_DATA_TYPE.STATE)).toBeNull(); }); }); it('should show different handling for whitespace-only strings', () => { const whitespaceInputs = [' ', '\t\n\r']; whitespaceInputs.forEach((input) => { // Direct function processes whitespace (strips it to empty, then fails regex test) const directResult = getNormalizedState(input); expect(directResult).toBeNull(); // PII interface also returns null (validates input first, but gets same result) expect(getNormalizedPII(input, PII_DATA_TYPE.STATE)).toBeNull(); }); // Handle empty string separately since PII interface validates it differently expect(getNormalizedState('')).toBeNull(); expect(getNormalizedPII('', PII_DATA_TYPE.STATE)).toBeNull(); }); }); });