mirror of
https://github.com/selfxyz/self.git
synced 2026-04-05 03:00:53 -04:00
SELF-1000: address passport data not found issue (#1329)
* update lock files * use isUserRegisteredWithAlternativeCSCA * update lock * fix building release version * aadhaar fix for public keys * fix aadhaar check and add tess * fix test types * fix mocked data * coderabbit feedback * update tests and remove cruft * update lock and aar file * fix script and building aar file, add assets for recovery
This commit is contained in:
@@ -3,193 +3,570 @@
|
||||
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.
|
||||
|
||||
import type { PassportData } from '@selfxyz/common/types';
|
||||
import { isPassportDataValid } from '@selfxyz/mobile-sdk-alpha';
|
||||
import type { SelfClient } from '@selfxyz/mobile-sdk-alpha';
|
||||
import { DocumentEvents } from '@selfxyz/mobile-sdk-alpha/constants/analytics';
|
||||
|
||||
// Import functions to test AFTER mocks are set up
|
||||
import {
|
||||
checkAndUpdateRegistrationStates,
|
||||
getAlternativeCSCA,
|
||||
} from '@/utils/proving/validateDocument';
|
||||
|
||||
// Mock the analytics module to avoid side effects in tests
|
||||
jest.mock('@/utils/analytics', () => ({
|
||||
__esModule: true,
|
||||
default: () => ({
|
||||
trackEvent: jest.fn(),
|
||||
}),
|
||||
}));
|
||||
let mockTrackEvent: jest.Mock;
|
||||
jest.mock('@/utils/analytics', () => {
|
||||
mockTrackEvent = jest.fn();
|
||||
return () => ({
|
||||
trackEvent: mockTrackEvent,
|
||||
});
|
||||
});
|
||||
|
||||
// Mock the passport data provider to avoid database operations
|
||||
const mockGetAllDocumentsDirectlyFromKeychain = jest.fn();
|
||||
const mockLoadSelectedDocumentDirectlyFromKeychain = jest.fn();
|
||||
const mockLoadPassportDataAndSecret = jest.fn();
|
||||
const mockSetSelectedDocument = jest.fn();
|
||||
const mockStorePassportData = jest.fn();
|
||||
const mockUpdateDocumentRegistrationState = jest.fn();
|
||||
const mockReStorePassportDataWithRightCSCA = jest.fn();
|
||||
|
||||
jest.mock('@/providers/passportDataProvider', () => ({
|
||||
getAllDocuments: jest.fn(),
|
||||
getAllDocumentsDirectlyFromKeychain: jest.fn((...args: unknown[]) =>
|
||||
mockGetAllDocumentsDirectlyFromKeychain(...args),
|
||||
),
|
||||
loadDocumentCatalog: jest.fn(),
|
||||
loadPassportDataAndSecret: jest.fn(),
|
||||
loadPassportDataAndSecret: jest.fn((...args: unknown[]) =>
|
||||
mockLoadPassportDataAndSecret(...args),
|
||||
),
|
||||
loadSelectedDocument: jest.fn(),
|
||||
setSelectedDocument: jest.fn(),
|
||||
storePassportData: jest.fn(),
|
||||
updateDocumentRegistrationState: jest.fn(),
|
||||
loadSelectedDocumentDirectlyFromKeychain: jest.fn((...args: unknown[]) =>
|
||||
mockLoadSelectedDocumentDirectlyFromKeychain(...args),
|
||||
),
|
||||
setSelectedDocument: jest.fn((...args: unknown[]) =>
|
||||
mockSetSelectedDocument(...args),
|
||||
),
|
||||
storePassportData: jest.fn((...args: unknown[]) =>
|
||||
mockStorePassportData(...args),
|
||||
),
|
||||
updateDocumentRegistrationState: jest.fn((...args: unknown[]) =>
|
||||
mockUpdateDocumentRegistrationState(...args),
|
||||
),
|
||||
reStorePassportDataWithRightCSCA: jest.fn((...args: unknown[]) =>
|
||||
mockReStorePassportDataWithRightCSCA(...args),
|
||||
),
|
||||
}));
|
||||
|
||||
// Reusable default deployed circuits for initial store state
|
||||
const defaultDeployedCircuits: {
|
||||
REGISTER: string[];
|
||||
REGISTER_ID: string[];
|
||||
DSC: string[];
|
||||
DSC_ID: string[];
|
||||
} = {
|
||||
REGISTER: ['test_register'],
|
||||
REGISTER_ID: ['test_register_id'],
|
||||
DSC: ['test_dsc'],
|
||||
DSC_ID: ['test_dsc_id'],
|
||||
};
|
||||
|
||||
// Mock the protocol store to avoid complex state management
|
||||
jest.mock('@selfxyz/mobile-sdk-alpha/stores', () => ({
|
||||
useProtocolStore: {
|
||||
getState: jest.fn(() => ({
|
||||
passport: {
|
||||
fetch_all: jest.fn(),
|
||||
deployed_circuits: {
|
||||
REGISTER: ['test_register'],
|
||||
REGISTER_ID: ['test_register_id'],
|
||||
DSC: ['test_dsc'],
|
||||
DSC_ID: ['test_dsc_id'],
|
||||
},
|
||||
commitment_tree: 'test_tree',
|
||||
alternative_csca: {},
|
||||
},
|
||||
id_card: {
|
||||
fetch_all: jest.fn(),
|
||||
deployed_circuits: {
|
||||
REGISTER: ['test_register'],
|
||||
REGISTER_ID: ['test_register_id'],
|
||||
DSC: ['test_dsc'],
|
||||
DSC_ID: ['test_dsc_id'],
|
||||
},
|
||||
commitment_tree: 'test_tree',
|
||||
alternative_csca: {},
|
||||
},
|
||||
})),
|
||||
const mockGetState = jest.fn(() => ({
|
||||
passport: {
|
||||
fetch_all: jest.fn(),
|
||||
deployed_circuits: { ...defaultDeployedCircuits },
|
||||
commitment_tree: 'test_tree',
|
||||
alternative_csca: {},
|
||||
},
|
||||
id_card: {
|
||||
fetch_all: jest.fn(),
|
||||
deployed_circuits: { ...defaultDeployedCircuits },
|
||||
commitment_tree: 'test_tree',
|
||||
alternative_csca: {},
|
||||
},
|
||||
aadhaar: {
|
||||
public_keys: [] as string[] | null,
|
||||
commitment_tree: 'test_tree',
|
||||
},
|
||||
}));
|
||||
|
||||
/**
|
||||
* Creates a Self SDK client with minimal mock adapters for tests.
|
||||
*/
|
||||
function createTestClient() {
|
||||
const { createSelfClient } = require('@selfxyz/mobile-sdk-alpha');
|
||||
return createSelfClient({
|
||||
config: {},
|
||||
adapters: {
|
||||
auth: { getPrivateKey: jest.fn() },
|
||||
scanner: { scan: jest.fn() },
|
||||
network: {
|
||||
http: { fetch: jest.fn() },
|
||||
ws: {
|
||||
connect: jest.fn(() => ({
|
||||
send: jest.fn(),
|
||||
close: jest.fn(),
|
||||
onMessage: jest.fn(),
|
||||
onError: jest.fn(),
|
||||
onClose: jest.fn(),
|
||||
})),
|
||||
},
|
||||
},
|
||||
crypto: {
|
||||
hash: jest.fn(),
|
||||
sign: jest.fn(),
|
||||
},
|
||||
documents: {
|
||||
loadDocumentCatalog: jest.fn(),
|
||||
loadDocumentById: jest.fn(),
|
||||
},
|
||||
},
|
||||
});
|
||||
const mockFetchAllTreesAndCircuits = jest.fn();
|
||||
const mockGetCommitmentTree = jest.fn();
|
||||
|
||||
jest.mock('@selfxyz/mobile-sdk-alpha/stores', () => ({
|
||||
useProtocolStore: {
|
||||
getState: jest.fn(() => mockGetState()),
|
||||
},
|
||||
fetchAllTreesAndCircuits: jest.fn((...args: unknown[]) =>
|
||||
mockFetchAllTreesAndCircuits(...args),
|
||||
),
|
||||
getCommitmentTree: jest.fn((...args: unknown[]) =>
|
||||
mockGetCommitmentTree(...args),
|
||||
),
|
||||
}));
|
||||
|
||||
// DRY helpers for repeated protocol state shapes in tests
|
||||
const emptyDeployedCircuits = {
|
||||
REGISTER: [] as string[],
|
||||
REGISTER_ID: [] as string[],
|
||||
DSC: [] as string[],
|
||||
DSC_ID: [] as string[],
|
||||
};
|
||||
|
||||
function buildModuleState(alternative: Record<string, unknown> = {}) {
|
||||
return {
|
||||
fetch_all: jest.fn(),
|
||||
deployed_circuits: { ...emptyDeployedCircuits },
|
||||
commitment_tree: 'test_tree',
|
||||
alternative_csca: alternative,
|
||||
};
|
||||
}
|
||||
|
||||
/** Sample ICAO-compliant MRZ string for parsing tests. */
|
||||
const validMrz = `P<UTOERIKSSON<<ANNA<MARIA<<<<<<<<<<<<<<<<<<<\nL898902C36UTO7408122F1204159ZE184226B<<<<<10`;
|
||||
function buildState(params?: {
|
||||
passportAlt?: Record<string, unknown>;
|
||||
idAlt?: Record<string, unknown>;
|
||||
aadhaarKeys?: string[] | null;
|
||||
}) {
|
||||
return {
|
||||
passport: buildModuleState(params?.passportAlt ?? {}),
|
||||
id_card: buildModuleState(params?.idAlt ?? {}),
|
||||
aadhaar: {
|
||||
public_keys: (params?.aadhaarKeys ?? []) as string[] | null,
|
||||
commitment_tree: 'test_tree',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/** Intentionally malformed MRZ string to exercise error handling. */
|
||||
const invalidMrz = 'NOT_A_VALID_MRZ';
|
||||
// Mock the validation utilities
|
||||
const mockIsUserRegisteredWithAlternativeCSCA = jest.fn();
|
||||
jest.mock('@selfxyz/common/utils/passports/validate', () => ({
|
||||
isUserRegisteredWithAlternativeCSCA: jest.fn((...args: unknown[]) =>
|
||||
mockIsUserRegisteredWithAlternativeCSCA(...args),
|
||||
),
|
||||
}));
|
||||
|
||||
describe('validateDocument - Real mobile-sdk-alpha Integration (PII-safe)', () => {
|
||||
it('should use the real isPassportDataValid function with synthetic passport data', () => {
|
||||
// This test verifies that we're using the real function, not a mock
|
||||
expect(typeof isPassportDataValid).toBe('function');
|
||||
|
||||
// The real function should be callable
|
||||
expect(typeof isPassportDataValid).toBe('function');
|
||||
|
||||
// Test with realistic, synthetic passport data (NEVER real user data)
|
||||
const mockPassportData: PassportData = {
|
||||
documentCategory: 'passport',
|
||||
mock: true,
|
||||
mrz: 'P<UTOD23145890<1233<6831101169<9408125F2206304<<<<<6',
|
||||
dsc: 'mock_dsc_data',
|
||||
eContent: [1, 2, 3, 4],
|
||||
passportMetadata: {
|
||||
cscaFound: true,
|
||||
eContentHashFunction: 'sha256',
|
||||
dg1HashFunction: 'sha256',
|
||||
signedAttrHashFunction: 'sha256',
|
||||
},
|
||||
dsc_parsed: {
|
||||
authorityKeyIdentifier: 'test_key_id',
|
||||
subjectPublicKeyInfo: {
|
||||
algorithm: { algorithm: '1.2.840.113549.1.1.1' },
|
||||
subjectPublicKey: new Uint8Array([1, 2, 3, 4]),
|
||||
},
|
||||
},
|
||||
csca_parsed: {
|
||||
subjectPublicKeyInfo: {
|
||||
algorithm: { algorithm: '1.2.840.113549.1.1.1' },
|
||||
subjectPublicKey: new Uint8Array([1, 2, 3, 4]),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const callbacks = {
|
||||
onPassportDataNull: jest.fn(),
|
||||
onPassportMetadataNull: jest.fn(),
|
||||
onDg1HashFunctionNull: jest.fn(),
|
||||
onEContentHashFunctionNull: jest.fn(),
|
||||
onSignedAttrHashFunctionNull: jest.fn(),
|
||||
onDg1HashMismatch: jest.fn(),
|
||||
onUnsupportedHashAlgorithm: jest.fn(),
|
||||
onDg1HashMissing: jest.fn(),
|
||||
};
|
||||
|
||||
// This should call the real function and not throw
|
||||
expect(() => {
|
||||
isPassportDataValid(mockPassportData, callbacks);
|
||||
}).not.toThrow();
|
||||
describe('getAlternativeCSCA', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should handle validation errors through callbacks', () => {
|
||||
const invalidPassportData = {
|
||||
documentCategory: 'passport',
|
||||
mock: true,
|
||||
// Missing required fields to trigger validation errors
|
||||
} as PassportData;
|
||||
it('should return public keys in Record format for Aadhaar with valid public keys', () => {
|
||||
const mockPublicKeys = ['key1', 'key2', 'key3'];
|
||||
mockGetState.mockReturnValue(buildState({ aadhaarKeys: mockPublicKeys }));
|
||||
|
||||
const callbacks = {
|
||||
onPassportDataNull: jest.fn(),
|
||||
onPassportMetadataNull: jest.fn(),
|
||||
onDg1HashFunctionNull: jest.fn(),
|
||||
onEContentHashFunctionNull: jest.fn(),
|
||||
onSignedAttrHashFunctionNull: jest.fn(),
|
||||
onDg1HashMismatch: jest.fn(),
|
||||
onUnsupportedHashAlgorithm: jest.fn(),
|
||||
onDg1HashMissing: jest.fn(),
|
||||
};
|
||||
const mockUseProtocolStore = { getState: mockGetState } as any;
|
||||
const result = getAlternativeCSCA(mockUseProtocolStore, 'aadhaar');
|
||||
|
||||
// This should call the real validation function and trigger callbacks
|
||||
const result = isPassportDataValid(invalidPassportData, callbacks);
|
||||
|
||||
// The real function should return false for invalid data
|
||||
expect(result).toBe(false);
|
||||
|
||||
// Some callbacks should have been called due to missing data
|
||||
expect(callbacks.onPassportMetadataNull).toHaveBeenCalled();
|
||||
expect(result).toEqual({
|
||||
public_key_0: 'key1',
|
||||
public_key_1: 'key2',
|
||||
public_key_2: 'key3',
|
||||
});
|
||||
});
|
||||
|
||||
it('should expose extractMRZInfo via a self client instance', () => {
|
||||
const client = createTestClient();
|
||||
expect(typeof client.extractMRZInfo).toBe('function');
|
||||
it('should return empty object for Aadhaar with no public keys', () => {
|
||||
mockGetState.mockReturnValue(buildState({ aadhaarKeys: null }));
|
||||
|
||||
const mockUseProtocolStore = { getState: mockGetState } as any;
|
||||
const result = getAlternativeCSCA(mockUseProtocolStore, 'aadhaar');
|
||||
|
||||
expect(result).toEqual({});
|
||||
});
|
||||
|
||||
it('parses a valid MRZ string', () => {
|
||||
const client = createTestClient();
|
||||
const info = client.extractMRZInfo(validMrz);
|
||||
expect(info.documentNumber).toBe('L898902C3');
|
||||
expect(info.validation).toBeDefined();
|
||||
expect(info.validation?.overall).toBe(true);
|
||||
it('should return empty object for Aadhaar with empty public keys array', () => {
|
||||
mockGetState.mockReturnValue(buildState({ aadhaarKeys: [] }));
|
||||
|
||||
const mockUseProtocolStore = { getState: mockGetState } as any;
|
||||
const result = getAlternativeCSCA(mockUseProtocolStore, 'aadhaar');
|
||||
|
||||
expect(result).toEqual({});
|
||||
});
|
||||
|
||||
it('throws on malformed MRZ input', () => {
|
||||
const client = createTestClient();
|
||||
expect(() => client.extractMRZInfo(invalidMrz)).toThrow();
|
||||
it('should return alternative_csca for passport', () => {
|
||||
const mockAlternativeCSCA = { csca1: 'cert1', csca2: 'cert2' };
|
||||
mockGetState.mockReturnValue(
|
||||
buildState({ passportAlt: mockAlternativeCSCA }),
|
||||
);
|
||||
|
||||
const mockUseProtocolStore = { getState: mockGetState } as any;
|
||||
const result = getAlternativeCSCA(mockUseProtocolStore, 'passport');
|
||||
|
||||
expect(result).toEqual(mockAlternativeCSCA);
|
||||
});
|
||||
|
||||
it('should return alternative_csca for id_card', () => {
|
||||
const mockAlternativeCSCA = { csca1: 'id_cert1', csca2: 'id_cert2' };
|
||||
mockGetState.mockReturnValue(buildState({ idAlt: mockAlternativeCSCA }));
|
||||
|
||||
const mockUseProtocolStore = { getState: mockGetState } as any;
|
||||
const result = getAlternativeCSCA(mockUseProtocolStore, 'id_card');
|
||||
|
||||
expect(result).toEqual(mockAlternativeCSCA);
|
||||
});
|
||||
|
||||
it('should return empty object for passport with no alternative_csca', () => {
|
||||
mockGetState.mockReturnValue(buildState());
|
||||
|
||||
const mockUseProtocolStore = { getState: mockGetState } as any;
|
||||
const result = getAlternativeCSCA(mockUseProtocolStore, 'passport');
|
||||
|
||||
expect(result).toEqual({});
|
||||
});
|
||||
});
|
||||
|
||||
describe('checkAndUpdateRegistrationStates', () => {
|
||||
let mockSelfClient: SelfClient;
|
||||
const mockPassportData = {
|
||||
documentCategory: 'passport',
|
||||
documentType: 'passport',
|
||||
mock: true,
|
||||
mrz: 'P<UTOD23145890<1233<6831101169<9408125F2206304<<<<<6',
|
||||
dsc: 'mock_dsc_data',
|
||||
eContent: [1, 2, 3, 4],
|
||||
passportMetadata: {
|
||||
cscaFound: true,
|
||||
eContentHashFunction: 'sha256',
|
||||
dg1HashFunction: 'sha256',
|
||||
signedAttrHashFunction: 'sha256',
|
||||
},
|
||||
dsc_parsed: {
|
||||
authorityKeyIdentifier: 'test_key_id',
|
||||
},
|
||||
csca_parsed: {},
|
||||
} as PassportData;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mockGetState.mockReturnValue(
|
||||
buildState({
|
||||
passportAlt: { csca1: 'cert1' },
|
||||
idAlt: { csca1: 'cert1' },
|
||||
aadhaarKeys: ['key1', 'key2'],
|
||||
}),
|
||||
);
|
||||
|
||||
mockSelfClient = {
|
||||
useProtocolStore: { getState: mockGetState },
|
||||
} as unknown as SelfClient;
|
||||
|
||||
mockFetchAllTreesAndCircuits.mockResolvedValue(undefined);
|
||||
mockGetCommitmentTree.mockReturnValue('mock_tree');
|
||||
});
|
||||
|
||||
it('should call reStorePassportDataWithRightCSCA when document is registered with alternative CSCA (passport)', async () => {
|
||||
const mockCSCA =
|
||||
'-----BEGIN CERTIFICATE-----\nMOCK_CSCA_CERT_DATA\n-----END CERTIFICATE-----';
|
||||
mockGetAllDocumentsDirectlyFromKeychain.mockResolvedValue({
|
||||
doc1: { data: mockPassportData },
|
||||
});
|
||||
mockLoadSelectedDocumentDirectlyFromKeychain.mockResolvedValue({
|
||||
data: mockPassportData,
|
||||
});
|
||||
mockLoadPassportDataAndSecret.mockResolvedValue(
|
||||
JSON.stringify({ data: mockPassportData, secret: 'test_secret' }),
|
||||
);
|
||||
mockIsUserRegisteredWithAlternativeCSCA.mockResolvedValue({
|
||||
isRegistered: true,
|
||||
csca: mockCSCA,
|
||||
});
|
||||
|
||||
await checkAndUpdateRegistrationStates(mockSelfClient);
|
||||
|
||||
expect(mockIsUserRegisteredWithAlternativeCSCA).toHaveBeenCalledWith(
|
||||
mockPassportData,
|
||||
'test_secret',
|
||||
expect.objectContaining({
|
||||
getCommitmentTree: expect.any(Function),
|
||||
getAltCSCA: expect.any(Function),
|
||||
}),
|
||||
);
|
||||
expect(mockReStorePassportDataWithRightCSCA).toHaveBeenCalledWith(
|
||||
mockPassportData,
|
||||
mockCSCA,
|
||||
);
|
||||
expect(mockUpdateDocumentRegistrationState).toHaveBeenCalledWith(
|
||||
'doc1',
|
||||
true,
|
||||
);
|
||||
expect(mockTrackEvent).toHaveBeenCalledWith(
|
||||
DocumentEvents.DOCUMENT_VALIDATED,
|
||||
expect.objectContaining({
|
||||
documentId: 'doc1',
|
||||
documentCategory: 'passport',
|
||||
mock: true,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should update registration state to false when document is not registered', async () => {
|
||||
mockGetAllDocumentsDirectlyFromKeychain.mockResolvedValue({
|
||||
doc1: { data: mockPassportData },
|
||||
});
|
||||
mockLoadSelectedDocumentDirectlyFromKeychain.mockResolvedValue({
|
||||
data: mockPassportData,
|
||||
});
|
||||
mockLoadPassportDataAndSecret.mockResolvedValue(
|
||||
JSON.stringify({ data: mockPassportData, secret: 'test_secret' }),
|
||||
);
|
||||
mockIsUserRegisteredWithAlternativeCSCA.mockResolvedValue({
|
||||
isRegistered: false,
|
||||
csca: null,
|
||||
});
|
||||
|
||||
await checkAndUpdateRegistrationStates(mockSelfClient);
|
||||
|
||||
expect(mockUpdateDocumentRegistrationState).toHaveBeenCalledWith(
|
||||
'doc1',
|
||||
false,
|
||||
);
|
||||
expect(mockReStorePassportDataWithRightCSCA).not.toHaveBeenCalled();
|
||||
expect(mockTrackEvent).not.toHaveBeenCalledWith(
|
||||
DocumentEvents.DOCUMENT_VALIDATED,
|
||||
expect.anything(),
|
||||
);
|
||||
});
|
||||
|
||||
it('should skip invalid passport data and track validation failure', async () => {
|
||||
const invalidData = {
|
||||
documentCategory: 'passport',
|
||||
mock: true,
|
||||
} as PassportData;
|
||||
mockGetAllDocumentsDirectlyFromKeychain.mockResolvedValue({
|
||||
doc1: { data: invalidData },
|
||||
});
|
||||
mockLoadSelectedDocumentDirectlyFromKeychain.mockResolvedValue({
|
||||
data: invalidData,
|
||||
});
|
||||
|
||||
await checkAndUpdateRegistrationStates(mockSelfClient);
|
||||
|
||||
expect(mockIsUserRegisteredWithAlternativeCSCA).not.toHaveBeenCalled();
|
||||
expect(mockUpdateDocumentRegistrationState).not.toHaveBeenCalled();
|
||||
expect(mockTrackEvent).toHaveBeenCalledWith(
|
||||
DocumentEvents.VALIDATE_DOCUMENT_FAILED,
|
||||
expect.objectContaining({
|
||||
error: 'Passport data is not valid',
|
||||
documentId: 'doc1',
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should skip document with missing authority key identifier', async () => {
|
||||
const dataWithoutKeyId = {
|
||||
...mockPassportData,
|
||||
dsc_parsed: {
|
||||
...mockPassportData.dsc_parsed,
|
||||
authorityKeyIdentifier: undefined,
|
||||
},
|
||||
};
|
||||
mockGetAllDocumentsDirectlyFromKeychain.mockResolvedValue({
|
||||
doc1: { data: dataWithoutKeyId },
|
||||
});
|
||||
mockLoadSelectedDocumentDirectlyFromKeychain.mockResolvedValue({
|
||||
data: dataWithoutKeyId,
|
||||
});
|
||||
|
||||
await checkAndUpdateRegistrationStates(mockSelfClient);
|
||||
|
||||
expect(mockFetchAllTreesAndCircuits).not.toHaveBeenCalled();
|
||||
expect(mockIsUserRegisteredWithAlternativeCSCA).not.toHaveBeenCalled();
|
||||
expect(mockTrackEvent).toHaveBeenCalledWith(
|
||||
DocumentEvents.VALIDATE_DOCUMENT_FAILED,
|
||||
expect.objectContaining({
|
||||
error: 'Authority key identifier is null',
|
||||
documentId: 'doc1',
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle multiple documents with mixed registration states', async () => {
|
||||
const doc1Data = { ...mockPassportData };
|
||||
const doc2Data = {
|
||||
...mockPassportData,
|
||||
documentCategory: 'id_card' as const,
|
||||
};
|
||||
const doc3Data = { ...mockPassportData };
|
||||
|
||||
mockGetAllDocumentsDirectlyFromKeychain.mockResolvedValue({
|
||||
doc1: { data: doc1Data },
|
||||
doc2: { data: doc2Data },
|
||||
doc3: { data: doc3Data },
|
||||
});
|
||||
|
||||
mockLoadSelectedDocumentDirectlyFromKeychain
|
||||
.mockResolvedValueOnce({ data: doc1Data })
|
||||
.mockResolvedValueOnce({ data: doc2Data })
|
||||
.mockResolvedValueOnce({ data: doc3Data });
|
||||
|
||||
mockLoadPassportDataAndSecret
|
||||
.mockResolvedValueOnce(
|
||||
JSON.stringify({ data: doc1Data, secret: 'secret1' }),
|
||||
)
|
||||
.mockResolvedValueOnce(
|
||||
JSON.stringify({ data: doc2Data, secret: 'secret2' }),
|
||||
)
|
||||
.mockResolvedValueOnce(
|
||||
JSON.stringify({ data: doc3Data, secret: 'secret3' }),
|
||||
);
|
||||
|
||||
mockIsUserRegisteredWithAlternativeCSCA
|
||||
.mockResolvedValueOnce({
|
||||
isRegistered: true,
|
||||
csca: '-----BEGIN CERTIFICATE-----\nMOCK_CSCA_CERT_DATA_1\n-----END CERTIFICATE-----',
|
||||
})
|
||||
.mockResolvedValueOnce({ isRegistered: false, csca: null })
|
||||
.mockResolvedValueOnce({
|
||||
isRegistered: true,
|
||||
csca: '-----BEGIN CERTIFICATE-----\nMOCK_CSCA_CERT_DATA_3\n-----END CERTIFICATE-----',
|
||||
});
|
||||
|
||||
await checkAndUpdateRegistrationStates(mockSelfClient);
|
||||
|
||||
expect(mockUpdateDocumentRegistrationState).toHaveBeenCalledTimes(3);
|
||||
expect(mockUpdateDocumentRegistrationState).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
'doc1',
|
||||
true,
|
||||
);
|
||||
expect(mockUpdateDocumentRegistrationState).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
'doc2',
|
||||
false,
|
||||
);
|
||||
expect(mockUpdateDocumentRegistrationState).toHaveBeenNthCalledWith(
|
||||
3,
|
||||
'doc3',
|
||||
true,
|
||||
);
|
||||
|
||||
expect(mockReStorePassportDataWithRightCSCA).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('should handle errors during registration check gracefully', async () => {
|
||||
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();
|
||||
|
||||
mockGetAllDocumentsDirectlyFromKeychain.mockResolvedValue({
|
||||
doc1: { data: mockPassportData },
|
||||
});
|
||||
mockLoadSelectedDocumentDirectlyFromKeychain.mockResolvedValue({
|
||||
data: mockPassportData,
|
||||
});
|
||||
mockLoadPassportDataAndSecret.mockResolvedValue(
|
||||
JSON.stringify({ data: mockPassportData, secret: 'test_secret' }),
|
||||
);
|
||||
mockIsUserRegisteredWithAlternativeCSCA.mockRejectedValue(
|
||||
new Error('Network error'),
|
||||
);
|
||||
|
||||
await checkAndUpdateRegistrationStates(mockSelfClient);
|
||||
|
||||
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining(
|
||||
'Error checking registration state for document doc1',
|
||||
),
|
||||
);
|
||||
expect(mockTrackEvent).toHaveBeenCalledWith(
|
||||
DocumentEvents.VALIDATE_DOCUMENT_FAILED,
|
||||
expect.objectContaining({
|
||||
error: 'Network error',
|
||||
documentId: 'doc1',
|
||||
}),
|
||||
);
|
||||
|
||||
consoleErrorSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('should track analytics events correctly for registered documents', async () => {
|
||||
mockGetAllDocumentsDirectlyFromKeychain.mockResolvedValue({
|
||||
doc1: { data: mockPassportData },
|
||||
});
|
||||
mockLoadSelectedDocumentDirectlyFromKeychain.mockResolvedValue({
|
||||
data: mockPassportData,
|
||||
});
|
||||
mockLoadPassportDataAndSecret.mockResolvedValue(
|
||||
JSON.stringify({ data: mockPassportData, secret: 'test_secret' }),
|
||||
);
|
||||
mockIsUserRegisteredWithAlternativeCSCA.mockResolvedValue({
|
||||
isRegistered: true,
|
||||
csca: '-----BEGIN CERTIFICATE-----\nMOCK_CSCA_CERT_DATA\n-----END CERTIFICATE-----',
|
||||
});
|
||||
|
||||
await checkAndUpdateRegistrationStates(mockSelfClient);
|
||||
|
||||
expect(mockTrackEvent).toHaveBeenCalledWith(
|
||||
DocumentEvents.DOCUMENT_VALIDATED,
|
||||
{
|
||||
documentId: 'doc1',
|
||||
documentCategory: 'passport',
|
||||
mock: true,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('should skip document when no passport data and secret is available', async () => {
|
||||
const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation();
|
||||
|
||||
mockGetAllDocumentsDirectlyFromKeychain.mockResolvedValue({
|
||||
doc1: { data: mockPassportData },
|
||||
});
|
||||
mockLoadSelectedDocumentDirectlyFromKeychain.mockResolvedValue({
|
||||
data: mockPassportData,
|
||||
});
|
||||
mockLoadPassportDataAndSecret.mockResolvedValue(null);
|
||||
|
||||
await checkAndUpdateRegistrationStates(mockSelfClient);
|
||||
|
||||
expect(consoleWarnSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining(
|
||||
'Skipping document doc1 - no passport data and secret',
|
||||
),
|
||||
);
|
||||
expect(mockIsUserRegisteredWithAlternativeCSCA).not.toHaveBeenCalled();
|
||||
|
||||
consoleWarnSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('should verify correct callbacks are passed to isUserRegisteredWithAlternativeCSCA', async () => {
|
||||
mockGetAllDocumentsDirectlyFromKeychain.mockResolvedValue({
|
||||
doc1: { data: mockPassportData },
|
||||
});
|
||||
mockLoadSelectedDocumentDirectlyFromKeychain.mockResolvedValue({
|
||||
data: mockPassportData,
|
||||
});
|
||||
mockLoadPassportDataAndSecret.mockResolvedValue(
|
||||
JSON.stringify({ data: mockPassportData, secret: 'test_secret' }),
|
||||
);
|
||||
mockIsUserRegisteredWithAlternativeCSCA.mockResolvedValue({
|
||||
isRegistered: false,
|
||||
csca: null,
|
||||
});
|
||||
|
||||
await checkAndUpdateRegistrationStates(mockSelfClient);
|
||||
|
||||
// Verify the callbacks object structure
|
||||
expect(mockIsUserRegisteredWithAlternativeCSCA).toHaveBeenCalledWith(
|
||||
expect.any(Object),
|
||||
'test_secret',
|
||||
expect.objectContaining({
|
||||
getCommitmentTree: expect.any(Function),
|
||||
getAltCSCA: expect.any(Function),
|
||||
}),
|
||||
);
|
||||
|
||||
// Verify the callbacks work correctly
|
||||
const callArgs = mockIsUserRegisteredWithAlternativeCSCA.mock.calls[0];
|
||||
const callbacks = callArgs[2];
|
||||
|
||||
// Test getCommitmentTree callback
|
||||
callbacks.getCommitmentTree('passport');
|
||||
expect(mockGetCommitmentTree).toHaveBeenCalledWith(
|
||||
mockSelfClient,
|
||||
'passport',
|
||||
);
|
||||
|
||||
// Test getAltCSCA callback
|
||||
const altCSCA = callbacks.getAltCSCA('passport');
|
||||
expect(mockGetState).toHaveBeenCalled();
|
||||
expect(altCSCA).toEqual({ csca1: 'cert1' });
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user