Files
self/packages/mobile-sdk-alpha/tests/client.test.ts
Aaron DeRuvo 7899c239cc Three/country picker (#1448)
* use 3.0 country picker

* get blurview working in app
add navigation adapter to sdk
render

* fix fonts and double view registration issues

* dont need this script as we use peer deps now

* fix our package installs





* prayed to the false idol of claude to resolve installing anon-aadhar from a specific commit from a monorepo

* fix route types


* add peer deps to demo

---------

Co-authored-by: Leszek Stachowski <leszek.stachowski@self.xyz>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2025-12-01 16:08:09 +01:00

231 lines
8.2 KiB
TypeScript

// SPDX-FileCopyrightText: 2025 Social Connect Labs, Inc.
// SPDX-License-Identifier: BUSL-1.1
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.
import { describe, expect, it, vi } from 'vitest';
import type { CryptoAdapter, DocumentsAdapter, NetworkAdapter, NFCScannerAdapter } from '../src';
import { createListenersMap, createSelfClient, SdkEvents } from '../src/index';
import type { AuthAdapter, NavigationAdapter } from '../src/types/public';
describe('createSelfClient', () => {
// Test eager validation during client creation
it('throws when scanner adapter missing during creation', () => {
expect(() =>
createSelfClient({
config: {},
// @ts-expect-error -- missing adapters
adapters: {
documents,
auth,
network,
crypto,
},
}),
).toThrow('scanner adapter not provided');
});
it('throws when network adapter missing during creation', () => {
// @ts-expect-error -- missing adapters
expect(() => createSelfClient({ config: {}, adapters: { scanner, crypto, documents, auth, navigation } })).toThrow(
'network adapter not provided',
);
});
it('throws when crypto adapter missing during creation', () => {
// @ts-expect-error -- missing adapters
expect(() => createSelfClient({ config: {}, adapters: { scanner, network, documents, auth, navigation } })).toThrow(
'crypto adapter not provided',
);
});
it('throws when documents adapter missing during creation', () => {
// @ts-expect-error -- missing adapters
expect(() => createSelfClient({ config: {}, adapters: { scanner, network, crypto, auth, navigation } })).toThrow(
'documents adapter not provided',
);
});
it('creates client successfully with all required adapters', () => {
const client = createSelfClient({
config: {},
adapters: { scanner, network, crypto, documents, auth, navigation },
listeners: new Map(),
});
expect(client).toBeTruthy();
});
it('scans document with provided adapter', async () => {
const scanMock = vi.fn().mockResolvedValue({ passportData: { mock: true } });
const client = createSelfClient({
config: {},
adapters: { scanner: { scan: scanMock }, network, crypto, documents, auth, navigation },
listeners: new Map(),
});
const result = await client.scanNFC({
passportNumber: '123',
dateOfBirth: '900101',
dateOfExpiry: '300101',
sessionId: 'test',
});
expect(result).toEqual({ passportData: { mock: true } });
expect(scanMock).toHaveBeenCalledWith(
expect.objectContaining({
passportNumber: '123',
dateOfBirth: '900101',
dateOfExpiry: '300101',
sessionId: 'test',
signal: expect.any(AbortSignal),
}),
);
});
it('propagates scanner errors', async () => {
const err = new Error('scan failed');
const scanMock = vi.fn().mockRejectedValue(err);
const client = createSelfClient({
config: {},
adapters: { scanner: { scan: scanMock }, network, crypto, documents, auth, navigation },
listeners: new Map(),
});
await expect(
client.scanNFC({ passportNumber: '123', dateOfBirth: '900101', dateOfExpiry: '300101', sessionId: 'test' }),
).rejects.toBe(err);
});
it('emits and unsubscribes events', () => {
const listeners = createListenersMap();
const passportNotSupportedListener = vi.fn();
const accountRecoveryChoiceListener = vi.fn();
const anotherAccountRecoveryChoiceListener = vi.fn();
listeners.addListener(SdkEvents.PROVING_PASSPORT_NOT_SUPPORTED, passportNotSupportedListener);
listeners.addListener(SdkEvents.PROVING_ACCOUNT_RECOVERY_REQUIRED, accountRecoveryChoiceListener);
listeners.addListener(SdkEvents.PROVING_ACCOUNT_RECOVERY_REQUIRED, anotherAccountRecoveryChoiceListener);
const client = createSelfClient({
config: {},
adapters: { scanner, network, crypto, documents, auth, navigation },
listeners: listeners.map,
});
client.emit(SdkEvents.PROVING_PASSPORT_NOT_SUPPORTED, { countryCode: 'test', documentCategory: 'passport' });
client.emit(SdkEvents.PROVING_ACCOUNT_RECOVERY_REQUIRED);
client.emit(SdkEvents.PROVING_REGISTER_ERROR_OR_FAILURE, { hasValidDocument: true });
expect(accountRecoveryChoiceListener).toHaveBeenCalledTimes(1);
expect(accountRecoveryChoiceListener).toHaveBeenCalledWith(undefined);
expect(anotherAccountRecoveryChoiceListener).toHaveBeenCalledTimes(1);
expect(anotherAccountRecoveryChoiceListener).toHaveBeenCalledWith(undefined);
expect(passportNotSupportedListener).toHaveBeenCalledWith({ countryCode: 'test', documentCategory: 'passport' });
expect(passportNotSupportedListener).toHaveBeenCalledTimes(1);
client.emit(SdkEvents.PROVING_PASSPORT_NOT_SUPPORTED, { countryCode: 'test', documentCategory: 'passport' });
client.emit(SdkEvents.PROVING_ACCOUNT_RECOVERY_REQUIRED);
client.emit(SdkEvents.PROVING_REGISTER_ERROR_OR_FAILURE, { hasValidDocument: true });
expect(passportNotSupportedListener).toHaveBeenCalledTimes(2);
expect(accountRecoveryChoiceListener).toHaveBeenCalledTimes(2);
expect(anotherAccountRecoveryChoiceListener).toHaveBeenCalledTimes(2);
});
it('parses MRZ via client', () => {
const client = createSelfClient({
config: {},
adapters: { scanner, network, crypto, documents, auth, navigation },
listeners: new Map(),
});
const sample = `P<UTOERIKSSON<<ANNA<MARIA<<<<<<<<<<<<<<<<<<<\nL898902C36UTO7408122F1204159ZE184226B<<<<<10`;
const info = client.extractMRZInfo(sample);
expect(info.documentNumber).toBe('L898902C3');
expect(info.validation?.overall).toBe(true);
});
describe('when analytics adapter is given', () => {
it('calls that adapter for trackEvent', () => {
const trackEvent = vi.fn();
const client = createSelfClient({
config: {},
adapters: {
navigation,
scanner,
network,
crypto,
documents,
analytics: { trackEvent },
auth: { getPrivateKey: () => Promise.resolve('stubbed-private-key') },
},
listeners: new Map(),
});
client.trackEvent('test_event');
expect(trackEvent).toHaveBeenCalledOnce();
expect(trackEvent).toHaveBeenCalledWith('test_event', undefined);
client.trackEvent('another_event', { foo: 'bar' });
expect(trackEvent).toHaveBeenCalledWith('another_event', { foo: 'bar' });
});
});
describe('when auth adapter is given', () => {
it('getPrivateKey becomes callable on the client', async () => {
const getPrivateKey = vi.fn(() => Promise.resolve('stubbed-private-key'));
const client = createSelfClient({
config: {},
adapters: { scanner, network, crypto, documents, navigation, auth: { getPrivateKey } },
listeners: new Map(),
});
await expect(client.getPrivateKey()).resolves.toBe('stubbed-private-key');
});
it('hasPrivateKey becomes callable on the client', async () => {
const getPrivateKey = vi.fn(() => Promise.resolve('stubbed-private-key'));
const client = createSelfClient({
config: {},
adapters: { scanner, network, crypto, documents, navigation, auth: { getPrivateKey } },
listeners: new Map(),
});
await expect(client.hasPrivateKey()).resolves.toBe(true);
});
});
});
const scanner: NFCScannerAdapter = {
scan: async () => ({ passportData: { mock: true } as any }),
};
const network: NetworkAdapter = {
http: { fetch: async () => new Response(null) },
ws: {
connect: () => ({
send: () => {},
close: () => {},
onMessage: () => {},
onError: () => {},
onClose: () => {},
}),
},
};
const crypto: CryptoAdapter = {
hash: async () => new Uint8Array(),
sign: async () => new Uint8Array(),
};
const auth: AuthAdapter = {
getPrivateKey: async () => 'secret',
};
const documents: DocumentsAdapter = {
loadDocumentCatalog: async () => ({ documents: [] }),
loadDocumentById: async () => null,
saveDocumentCatalog: async () => {},
saveDocument: async () => {},
deleteDocument: async () => {},
};
const navigation: NavigationAdapter = {
goBack: vi.fn(),
goTo: vi.fn(),
};