From 021754cd0e4af478857a9518a98b377611d55d0a Mon Sep 17 00:00:00 2001 From: Justin Hernandez Date: Wed, 28 Jan 2026 22:15:49 -0800 Subject: [PATCH 1/4] add country document json info as static asset --- .../src/data/country-document-types.json | 256 ++++++++++++++++++ .../src/documents/useCountries.tsx | 37 +-- 2 files changed, 262 insertions(+), 31 deletions(-) create mode 100644 packages/mobile-sdk-alpha/src/data/country-document-types.json diff --git a/packages/mobile-sdk-alpha/src/data/country-document-types.json b/packages/mobile-sdk-alpha/src/data/country-document-types.json new file mode 100644 index 0000000000..a535f1829a --- /dev/null +++ b/packages/mobile-sdk-alpha/src/data/country-document-types.json @@ -0,0 +1,256 @@ +{ + "ABW": ["p", "i"], + "AFG": ["p"], + "AGO": ["p", "i"], + "AIA": ["p", "i"], + "ALA": ["p", "i"], + "ALB": ["p", "i"], + "AND": ["p", "i"], + "ARE": ["p", "i"], + "ARG": ["p", "i"], + "ARM": ["p", "i"], + "ASM": ["p", "i"], + "ATA": ["p", "i"], + "ATF": ["p", "i"], + "ATG": ["p", "i"], + "AUS": ["p", "i"], + "AUT": ["p", "i"], + "AZE": ["p", "i"], + "BDI": ["p", "i"], + "BEL": ["p", "i"], + "BEN": ["p", "i"], + "BES": ["p", "i"], + "BFA": ["p", "i"], + "BGD": ["p", "i"], + "BGR": ["p", "i"], + "BHR": ["p", "i"], + "BHS": ["p", "i"], + "BIH": ["p", "i"], + "BLM": ["p", "i"], + "BLR": ["p", "i"], + "BLZ": ["p", "i"], + "BMU": ["p", "i"], + "BOL": ["p", "i"], + "BRA": ["p", "i"], + "BRB": ["p", "i"], + "BRN": ["p", "i"], + "BTN": ["p", "i"], + "BVT": ["p", "i"], + "BWA": ["p", "i"], + "CAF": ["p", "i"], + "CAN": ["p", "i"], + "CCK": ["p", "i"], + "CHE": ["p", "i"], + "CHL": ["p", "i"], + "CHN": ["p", "i"], + "CIV": ["p", "i"], + "CMR": ["p", "i"], + "COD": ["p", "i"], + "COG": ["p", "i"], + "COK": ["p", "i"], + "COL": ["p", "i"], + "COM": ["p", "i"], + "CPV": ["p", "i"], + "CRI": ["p", "i"], + "CUB": ["p", "i"], + "CUW": ["p", "i"], + "CXR": ["p", "i"], + "CYM": ["p", "i"], + "CYP": ["p", "i"], + "CZE": ["p", "i"], + "D<<": ["p", "i"], + "DJI": ["p", "i"], + "DMA": ["p", "i"], + "DNK": ["p", "i"], + "DOM": ["p", "i"], + "DZA": ["p", "i"], + "ECU": ["p", "i"], + "EGY": [], + "ERI": ["p", "i"], + "ESH": ["p", "i"], + "ESP": ["p", "i"], + "EST": ["p", "i"], + "ETH": ["p", "i"], + "EUE": ["p", "i"], + "FIN": ["p", "i"], + "FJI": ["p", "i"], + "FLK": ["p", "i"], + "FRA": ["p", "i"], + "FRO": ["p", "i"], + "FSM": ["p", "i"], + "GAB": ["p", "i"], + "GBR": ["p", "i"], + "GEO": ["p", "i"], + "GGY": ["p", "i"], + "GHA": ["p", "i"], + "GIB": ["p", "i"], + "GIN": ["p", "i"], + "GLP": ["p", "i"], + "GMB": ["p", "i"], + "GNB": ["p", "i"], + "GNQ": ["p", "i"], + "GRC": ["p", "i"], + "GRD": ["p", "i"], + "GRL": ["p", "i"], + "GTM": ["p", "i"], + "GUF": ["p", "i"], + "GUM": ["p", "i"], + "GUY": ["p", "i"], + "HKG": ["p", "i"], + "HMD": ["p", "i"], + "HND": ["p", "i"], + "HRV": ["p", "i"], + "HTI": ["p", "i"], + "HUN": ["p", "i"], + "IDN": ["p", "i"], + "IMN": ["p", "i"], + "IND": ["p", "a"], + "IOT": ["p", "i"], + "IRL": ["p", "i"], + "IRN": ["p", "i"], + "IRQ": ["p", "i"], + "ISL": ["p", "i"], + "ISR": ["p", "i"], + "ITA": ["p", "i"], + "JAM": ["p", "i"], + "JEY": ["p", "i"], + "JOR": ["p", "i"], + "JPN": ["p", "i"], + "KAZ": ["p", "i"], + "KEN": ["p", "i"], + "KGZ": ["p", "i"], + "KHM": ["p", "i"], + "KIR": ["p", "i"], + "KNA": ["p", "i"], + "KOR": ["p", "i"], + "KWT": ["p", "i"], + "LAO": ["p", "i"], + "LBN": ["p", "i"], + "LBR": ["p", "i"], + "LBY": ["p", "i"], + "LCA": ["p", "i"], + "LIE": ["p", "i"], + "LKA": ["p", "i"], + "LSO": ["p", "i"], + "LTU": ["p", "i"], + "LUX": ["p", "i"], + "LVA": ["p", "i"], + "MAC": ["p", "i"], + "MAF": ["p", "i"], + "MAR": ["p", "i"], + "MCO": ["p", "i"], + "MDA": ["p", "i"], + "MDG": ["p", "i"], + "MDV": ["p", "i"], + "MEX": ["p", "i"], + "MHL": ["p", "i"], + "MKD": ["p", "i"], + "MLI": ["p", "i"], + "MLT": ["p", "i"], + "MMR": ["p", "i"], + "MNE": ["p", "i"], + "MNG": ["p", "i"], + "MNP": ["p", "i"], + "MOZ": ["p", "i"], + "MRT": ["p", "i"], + "MSR": ["p", "i"], + "MTQ": ["p", "i"], + "MUS": ["p", "i"], + "MWI": ["p", "i"], + "MYS": ["p", "i"], + "MYT": ["p", "i"], + "NAM": ["p", "i"], + "NCL": ["p", "i"], + "NER": ["p", "i"], + "NFK": ["p", "i"], + "NGA": ["p", "i"], + "NIC": ["p", "i"], + "NIU": ["p", "i"], + "NLD": ["p", "i"], + "NOR": ["p", "i"], + "NPL": ["p", "i"], + "NRU": ["p", "i"], + "NZL": ["p", "i"], + "OMN": ["p", "i"], + "PAK": ["p", "i"], + "PAN": ["p", "i"], + "PCN": ["p", "i"], + "PER": ["p", "i"], + "PHL": ["p", "i"], + "PLW": ["p", "i"], + "PNG": ["p", "i"], + "POL": ["p", "i"], + "PRI": ["p", "i"], + "PRK": ["p", "i"], + "PRT": ["p", "i"], + "PRY": ["p", "i"], + "PSE": ["p", "i"], + "PYF": ["p", "i"], + "QAT": ["p", "i"], + "REU": ["p", "i"], + "ROU": ["p", "i"], + "RUS": ["p", "i"], + "RWA": ["p", "i"], + "SAU": ["p", "i"], + "SDN": ["p", "i"], + "SEN": ["p", "i"], + "SGP": ["p", "i"], + "SGS": ["p", "i"], + "SHN": ["p", "i"], + "SJM": ["p", "i"], + "SLB": ["p", "i"], + "SLE": ["p", "i"], + "SLV": ["p", "i"], + "SMR": ["p", "i"], + "SOM": ["p", "i"], + "SPM": ["p", "i"], + "SRB": ["p", "i"], + "SSD": ["p", "i"], + "STP": ["p", "i"], + "SUR": ["p", "i"], + "SVK": ["p", "i"], + "SVN": ["p", "i"], + "SWE": ["p", "i"], + "SWZ": ["p", "i"], + "SXM": ["p", "i"], + "SYC": ["p", "i"], + "SYR": ["p", "i"], + "TCA": ["p", "i"], + "TCD": ["p", "i"], + "TGO": ["p", "i"], + "THA": ["p", "i"], + "TJK": ["p", "i"], + "TKL": ["p", "i"], + "TKM": ["p", "i"], + "TLS": ["p", "i"], + "TON": ["p", "i"], + "TTO": ["p", "i"], + "TUN": ["p", "i"], + "TUR": ["p", "i"], + "TUV": ["p", "i"], + "TWN": ["p", "i"], + "TZA": ["p", "i"], + "UGA": ["p", "i"], + "UKR": ["p", "i"], + "UMI": ["p", "i"], + "UNO": ["p", "i"], + "URY": ["p", "i"], + "USA": ["p", "i"], + "UZB": ["p", "i"], + "VAT": ["p", "i"], + "VCT": ["p", "i"], + "VEN": ["p", "i"], + "VGB": ["p", "i"], + "VIR": ["p", "i"], + "VNM": ["p", "i"], + "VUT": ["p", "i"], + "WLF": ["p", "i"], + "WSM": ["p", "i"], + "XCE": ["p", "i"], + "XOM": ["p", "i"], + "XPO": ["p", "i"], + "YEM": ["p", "i"], + "ZAF": ["p", "i"], + "ZMB": ["p", "i"], + "ZWE": ["p", "i"] +} diff --git a/packages/mobile-sdk-alpha/src/documents/useCountries.tsx b/packages/mobile-sdk-alpha/src/documents/useCountries.tsx index f8adae9e2a..67acccc00d 100644 --- a/packages/mobile-sdk-alpha/src/documents/useCountries.tsx +++ b/packages/mobile-sdk-alpha/src/documents/useCountries.tsx @@ -2,12 +2,14 @@ // SPDX-License-Identifier: BUSL-1.1 // NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE. -import { useEffect, useMemo, useState } from 'react'; +import { useMemo } from 'react'; import { getCountry } from 'react-native-localize'; import { commonNames } from '@selfxyz/common'; import { alpha2ToAlpha3 } from '@selfxyz/common/constants/countries'; +import countryDocumentTypesData from '../data/country-document-types.json'; + export interface CountryData { [countryCode: string]: string[]; } @@ -29,38 +31,11 @@ function getUserCountryCode(): string | null { } return null; } + export function useCountries() { - const [countryData, setCountryData] = useState({}); - const [loading, setLoading] = useState(true); + const countryData = countryDocumentTypesData as CountryData; const userCountryCode = useMemo(getUserCountryCode, []); - useEffect(() => { - const controller = new AbortController(); - const fetchCountryData = async () => { - try { - const response = await fetch('https://api.staging.self.xyz/id-picker', { - signal: controller.signal, - }); - const result = await response.json(); - - if (result.status === 'success') { - setCountryData(result.data); - // if (__DEV__) { - // console.log('Set country data:', result.data); - // } - } else { - console.error('API returned non-success status:', result.status); - } - } catch (error) { - console.error('Error fetching country data:', error); - } finally { - setLoading(false); - } - }; - fetchCountryData(); - return () => controller.abort(); - }, []); - const countryList = useMemo(() => { const allCountries = Object.keys(countryData).map(countryCode => ({ key: countryCode, @@ -77,5 +52,5 @@ export function useCountries() { const showSuggestion = userCountryCode && countryData[userCountryCode]; - return { countryData, countryList, loading, userCountryCode, showSuggestion }; + return { countryData, countryList, loading: false, userCountryCode, showSuggestion }; } From 5bd72604a98b8f243d23c68275ddee140c61f1dd Mon Sep 17 00:00:00 2001 From: Justin Hernandez Date: Wed, 28 Jan 2026 22:28:07 -0800 Subject: [PATCH 2/4] add staleness test --- .../tests/data/country-data-sync.test.ts | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 packages/mobile-sdk-alpha/tests/data/country-data-sync.test.ts diff --git a/packages/mobile-sdk-alpha/tests/data/country-data-sync.test.ts b/packages/mobile-sdk-alpha/tests/data/country-data-sync.test.ts new file mode 100644 index 0000000000..264f1a0716 --- /dev/null +++ b/packages/mobile-sdk-alpha/tests/data/country-data-sync.test.ts @@ -0,0 +1,45 @@ +// 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 } from 'vitest'; + +import countryDocumentTypesData from '../../src/data/country-document-types.json'; + +describe('Country data synchronization', () => { + it('bundled data should match API response', async () => { + // Fetch current data from staging API with timeout + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), 5000); + + let response: Response; + try { + response = await fetch('https://api.staging.self.xyz/id-picker', { + signal: controller.signal, + }); + } catch (error) { + clearTimeout(timeoutId); + if (error instanceof Error && error.name === 'AbortError') { + throw new Error('API request timed out after 5 seconds'); + } + throw error; + } finally { + clearTimeout(timeoutId); + } + + expect(response.ok).toBe(true); + + const result = await response.json(); + expect(result.status).toBe('success'); + + const apiData = result.data; + const bundledData = countryDocumentTypesData; + + // Compare the data structures + expect(bundledData).toEqual(apiData); + + // If this test fails, it means the API has been updated with new countries + // or document types that aren't in the bundled data yet. + // To fix: Update src/data/country-document-types.json with the latest API data. + }, 10000); // 10s Vitest timeout +}); From 400dccb86a5de4761f42635122d18489328592db Mon Sep 17 00:00:00 2001 From: Justin Hernandez Date: Fri, 30 Jan 2026 09:44:24 -0800 Subject: [PATCH 3/4] update test --- .../country-data-sync.integration.test.ts | 99 +++++++++++++++++++ .../tests/data/country-data-sync.test.ts | 45 --------- 2 files changed, 99 insertions(+), 45 deletions(-) create mode 100644 packages/mobile-sdk-alpha/tests/data/country-data-sync.integration.test.ts delete mode 100644 packages/mobile-sdk-alpha/tests/data/country-data-sync.test.ts diff --git a/packages/mobile-sdk-alpha/tests/data/country-data-sync.integration.test.ts b/packages/mobile-sdk-alpha/tests/data/country-data-sync.integration.test.ts new file mode 100644 index 0000000000..00d89ab4ff --- /dev/null +++ b/packages/mobile-sdk-alpha/tests/data/country-data-sync.integration.test.ts @@ -0,0 +1,99 @@ +// 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. + +/** + * Integration test for country data synchronization. + * + * This test verifies that the bundled country-document-types.json matches + * the staging API response. It gracefully skips when network is unavailable + * to avoid CI flakiness from transient network issues. + * + * To run integration tests only: yarn test --grep="integration" + * To skip integration tests: yarn test --grep="^(?!.*integration)" + */ + +import { describe, expect, it } from 'vitest'; + +import countryDocumentTypesData from '../../src/data/country-document-types.json'; + +/** + * Helper to check if an error is a network-related error that should cause + * the test to skip rather than fail. + */ +function isNetworkError(error: unknown): boolean { + if (!(error instanceof Error)) return false; + + const networkErrorPatterns = [ + 'ENOTFOUND', // DNS resolution failed + 'ECONNREFUSED', // Connection refused + 'ECONNRESET', // Connection reset + 'ETIMEDOUT', // Connection timed out + 'EAI_AGAIN', // DNS temporary failure + 'ENETUNREACH', // Network unreachable + 'EHOSTUNREACH', // Host unreachable + 'fetch failed', // Generic fetch failure + 'network', // Generic network error + 'AbortError', // Request aborted (timeout) + ]; + + const errorMessage = error.message.toLowerCase(); + const errorName = error.name; + + return networkErrorPatterns.some( + (pattern) => + errorMessage.includes(pattern.toLowerCase()) || + errorName === pattern || + ('cause' in error && + error.cause instanceof Error && + error.cause.message.toLowerCase().includes(pattern.toLowerCase())), + ); +} + +describe('Country data synchronization [integration]', () => { + it('bundled data should match API response', async ({ skip }) => { + // Fetch current data from staging API with timeout + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), 5000); + + let response: Response; + try { + response = await fetch('https://api.staging.self.xyz/id-picker', { + signal: controller.signal, + }); + } catch (error) { + // Network errors should skip the test, not fail it + if (isNetworkError(error)) { + skip(); + return; + } + throw error; + } finally { + clearTimeout(timeoutId); + } + + // Non-2xx responses that aren't network errors should also skip + // (e.g., 503 Service Unavailable, 502 Bad Gateway) + if (!response.ok) { + if (response.status >= 500) { + skip(); + return; + } + // 4xx errors are likely real issues, so we let them fail + expect.fail(`API returned ${response.status}: ${response.statusText}`); + } + + const result = await response.json(); + expect(result.status).toBe('success'); + + const apiData = result.data; + const bundledData = countryDocumentTypesData; + + // Compare the data structures + expect(bundledData).toEqual(apiData); + + // If this test fails, it means the API has been updated with new countries + // or document types that aren't in the bundled data yet. + // To fix: Update src/data/country-document-types.json with the latest API data. + }, 10000); // 10s Vitest timeout +}); diff --git a/packages/mobile-sdk-alpha/tests/data/country-data-sync.test.ts b/packages/mobile-sdk-alpha/tests/data/country-data-sync.test.ts deleted file mode 100644 index 264f1a0716..0000000000 --- a/packages/mobile-sdk-alpha/tests/data/country-data-sync.test.ts +++ /dev/null @@ -1,45 +0,0 @@ -// 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 } from 'vitest'; - -import countryDocumentTypesData from '../../src/data/country-document-types.json'; - -describe('Country data synchronization', () => { - it('bundled data should match API response', async () => { - // Fetch current data from staging API with timeout - const controller = new AbortController(); - const timeoutId = setTimeout(() => controller.abort(), 5000); - - let response: Response; - try { - response = await fetch('https://api.staging.self.xyz/id-picker', { - signal: controller.signal, - }); - } catch (error) { - clearTimeout(timeoutId); - if (error instanceof Error && error.name === 'AbortError') { - throw new Error('API request timed out after 5 seconds'); - } - throw error; - } finally { - clearTimeout(timeoutId); - } - - expect(response.ok).toBe(true); - - const result = await response.json(); - expect(result.status).toBe('success'); - - const apiData = result.data; - const bundledData = countryDocumentTypesData; - - // Compare the data structures - expect(bundledData).toEqual(apiData); - - // If this test fails, it means the API has been updated with new countries - // or document types that aren't in the bundled data yet. - // To fix: Update src/data/country-document-types.json with the latest API data. - }, 10000); // 10s Vitest timeout -}); From 0684d4d90f5279f5338f0ee56b27e08e63f2f2ad Mon Sep 17 00:00:00 2001 From: Justin Hernandez Date: Fri, 30 Jan 2026 09:51:22 -0800 Subject: [PATCH 4/4] formatting --- .../tests/data/country-data-sync.integration.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mobile-sdk-alpha/tests/data/country-data-sync.integration.test.ts b/packages/mobile-sdk-alpha/tests/data/country-data-sync.integration.test.ts index 00d89ab4ff..76be2be586 100644 --- a/packages/mobile-sdk-alpha/tests/data/country-data-sync.integration.test.ts +++ b/packages/mobile-sdk-alpha/tests/data/country-data-sync.integration.test.ts @@ -41,7 +41,7 @@ function isNetworkError(error: unknown): boolean { const errorName = error.name; return networkErrorPatterns.some( - (pattern) => + pattern => errorMessage.includes(pattern.toLowerCase()) || errorName === pattern || ('cause' in error &&