/** * Integration tests for report API (CMC, CoinGecko) * Uses native fetch + http server (no deprecated supertest) */ import { createServer } from 'http'; import express from 'express'; import reportRoutes from './report'; import { getCanonicalTokenBySymbol } from '../../config/canonical-tokens'; jest.mock('../../database/repositories/token-repo', () => ({ TokenRepository: jest.fn().mockImplementation(() => ({ getToken: jest.fn().mockResolvedValue(null), })), })); jest.mock('../../database/repositories/market-data-repo', () => ({ MarketDataRepository: jest.fn().mockImplementation(() => ({ getMarketData: jest.fn().mockResolvedValue(null), })), })); jest.mock('../../database/repositories/pool-repo', () => ({ PoolRepository: jest.fn().mockImplementation(() => ({ getPoolsByToken: jest.fn().mockResolvedValue([]), getPoolsByChain: jest.fn().mockResolvedValue([]), })), })); jest.mock('../../indexer/cross-chain-indexer', () => ({ buildCrossChainReport: jest.fn().mockResolvedValue({ generatedAt: '2026-03-30T00:00:00.000Z', chainId: 138, crossChainPools: [], volumeByLane: [], atomicSwapVolume24h: 0, bridgeVolume24hTotal: 0, events: [], }), })); jest.mock('../middleware/cache'); function createApp() { const app = express(); app.use('/api/v1/report', reportRoutes); return app; } async function startServer(app: express.Application): Promise<{ server: ReturnType; baseUrl: string }> { const server = createServer(app); await new Promise((resolve) => server.listen(0, () => resolve())); const port = (server.address() as { port: number }).port; return { server, baseUrl: `http://127.0.0.1:${port}` }; } describe('Report API', () => { let server: ReturnType; let baseUrl: string; beforeAll(async () => { const app = createApp(); const started = await startServer(app); server = started.server; baseUrl = started.baseUrl; }); afterAll(async () => { await new Promise((resolve, reject) => { server.close((error) => { if (error) { reject(error); return; } resolve(); }); }); }); describe('GET /api/v1/report/cmc', () => { it('returns 200 with cmc format', async () => { const res = await fetch(`${baseUrl}/api/v1/report/cmc?chainId=138`); expect(res.status).toBe(200); const body = (await res.json()) as Record; expect(body).toHaveProperty('generatedAt'); expect(body).toHaveProperty('chainId', 138); expect(body).toHaveProperty('format', 'coinmarketcap-dex'); expect(body).toHaveProperty('tokens'); expect(Array.isArray(body.tokens)).toBe(true); }); it('accepts chainId 651940', async () => { const res = await fetch(`${baseUrl}/api/v1/report/cmc?chainId=651940`); expect(res.status).toBe(200); const body = (await res.json()) as Record; expect(body.chainId).toBe(651940); }); it('enriches Mainnet cWUSDC with supply proof fields for CMC reports', async () => { const res = await fetch(`${baseUrl}/api/v1/report/cmc?chainId=1`); expect(res.status).toBe(200); const body = (await res.json()) as Record; const cwusdc = body.tokens.find((token: Record) => token.symbol === 'cWUSDC'); expect(cwusdc).toMatchObject({ contract_address: '0x2de5f116bfce3d0f922d9c8351e0c5fc24b9284a', total_supply: 10451316981.309788, total_supply_raw: '10451316981309788', circulating_supply: 10451316981.309788, market_cap: 10451316981.309788, supply_proof_provenance: expect.objectContaining({ status: 'ready_for_tracker_review', }), }); expect(cwusdc.tracker_caveats).toEqual(expect.arrayContaining([expect.stringContaining('on-chain supply proof')])); }); }); describe('GET /api/v1/report/coingecko', () => { it('returns 200 with coingecko format', async () => { const res = await fetch(`${baseUrl}/api/v1/report/coingecko?chainId=138`); expect(res.status).toBe(200); const body = (await res.json()) as Record; expect(body).toHaveProperty('generatedAt'); expect(body).toHaveProperty('chainId', 138); expect(body).toHaveProperty('format', 'coingecko-submission'); expect(body).toHaveProperty('tokens'); expect(Array.isArray(body.tokens)).toBe(true); }); it('enriches Mainnet cWUSDC with supply proof, circulating supply, and market cap', async () => { const res = await fetch(`${baseUrl}/api/v1/report/coingecko?chainId=1`); expect(res.status).toBe(200); const body = (await res.json()) as Record; const cwusdc = body.tokens.find((token: Record) => token.symbol === 'cWUSDC'); expect(cwusdc).toMatchObject({ contract_address: '0x2de5f116bfce3d0f922d9c8351e0c5fc24b9284a', total_supply: 10451316981.309788, total_supply_raw: '10451316981309788', circulating_supply: 10451316981.309788, circulating_supply_formula: 'circulatingSupply = totalSupply - protocolControlledNonCirculatingBalances', supply_proof_provenance: expect.objectContaining({ schema: 'mainnet-cwusdc-supply-proof/v1', referenceBlock: 25047586, }), market_data: expect.objectContaining({ current_price: { usd: 1 }, market_cap: 10451316981.309788, }), }); expect(cwusdc.tracker_caveats).toEqual(expect.arrayContaining([expect.stringContaining('No public tracker')])); }); it('surfaces GRU v2 deployment-status pools in tracker-facing token reports', async () => { const res = await fetch(`${baseUrl}/api/v1/report/coingecko?chainId=1`); expect(res.status).toBe(200); const body = (await res.json()) as Record; const cwusdc = body.tokens.find((token: Record) => token.symbol === 'cWUSDC'); expect(cwusdc.liquidity_pools).toEqual( expect.arrayContaining([ expect.objectContaining({ pool_address: '0x69776fc607e9eda8042e320e7e43f54d06c68f0e', source: 'gru-v2-deployment-status', status: 'live', role: 'defense', }), ]) ); }); it('surfaces explicit supply-proof gaps for Mainnet GRU assets without proof artifacts', async () => { const res = await fetch(`${baseUrl}/api/v1/report/coingecko?chainId=1`); expect(res.status).toBe(200); const body = (await res.json()) as Record; const cwusdt = body.tokens.find((token: Record) => token.symbol === 'cWUSDT'); expect(cwusdt).toMatchObject({ contract_address: '0xaf5017d0163ecb99d9b5d94e3b4d7b09af44d8ae', supply_proof_provenance: { source: 'missing-supply-proof', status: 'proof_required', }, }); expect(cwusdt).not.toHaveProperty('total_supply'); expect(cwusdt.tracker_caveats).toEqual(expect.arrayContaining([expect.stringContaining('tracker-grade supply proof')])); }); }); describe('GET /api/v1/report/all', () => { it('includes GRU transport summary for operator visibility', async () => { const res = await fetch(`${baseUrl}/api/v1/report/all?chainId=138`); expect(res.status).toBe(200); const body = (await res.json()) as Record; expect(body.gruTransport?.system?.name).toBe('GRU Monetary Transport Layer'); expect(body.gruTransport?.summary).toMatchObject({ transportPairs: expect.any(Number), runtimeReadyTransportPairs: expect.any(Number), }); expect(body.gruTransport?.gasAssetFamilies).toEqual( expect.arrayContaining([ expect.objectContaining({ familyKey: 'eth_l2', backingMode: 'hybrid_cap', }), ]) ); }); it('fills canonical fallback usd pricing when market data is absent', async () => { const weth = getCanonicalTokenBySymbol(138, 'WETH'); expect(weth?.addresses[138]).toBeTruthy(); const wethAddress = String(weth?.addresses[138]).toLowerCase(); const res = await fetch(`${baseUrl}/api/v1/report/all?chainId=138`); expect(res.status).toBe(200); const body = (await res.json()) as Record; const tokens138 = body.tokens?.['138']; expect(Array.isArray(tokens138)).toBe(true); const wethEntry = tokens138.find((token: Record) => token.address === wethAddress); expect(wethEntry).toMatchObject({ symbol: 'WETH', decimals: 18, market: expect.objectContaining({ priceUsd: 2490, volume24h: 0, liquidityUsd: 0, lastUpdated: '2026-04-15T00:00:00.000Z', }), }); }); it('includes Mainnet cWUSDC supply proof enrichment in unified reports', async () => { const res = await fetch(`${baseUrl}/api/v1/report/all?chainId=1`); expect(res.status).toBe(200); const body = (await res.json()) as Record; const cwusdc = body.tokens?.['1']?.find((token: Record) => token.symbol === 'cWUSDC'); expect(cwusdc).toMatchObject({ totalSupply: '10451316981.309788', totalSupplyRaw: '10451316981309788', circulatingSupply: '10451316981.309788', circulatingSupplyFormula: 'circulatingSupply = totalSupply - protocolControlledNonCirculatingBalances', market: expect.objectContaining({ priceUsd: 1, marketCapUsd: 10451316981.309788, }), supplyProofProvenance: expect.objectContaining({ schema: 'mainnet-cwusdc-supply-proof/v1', referenceBlock: 25047586, }), }); }); it('distinguishes proof-gated Mainnet cW assets from deterministic placeholder bindings', async () => { const res = await fetch(`${baseUrl}/api/v1/report/all?chainId=1`); expect(res.status).toBe(200); const body = (await res.json()) as Record; const proofGated = body.tokens?.['1']?.find((entry: Record) => entry.symbol === 'cWUSDT'); expect(proofGated).toMatchObject({ supplyProofProvenance: { source: 'missing-supply-proof', status: 'proof_required', }, }); expect(proofGated.totalSupply).toBeUndefined(); expect(proofGated.trackerCaveats).toEqual(expect.arrayContaining([expect.stringContaining('proof artifact')])); const placeholderSymbols = ['cWBTC', 'cWETH']; for (const symbol of placeholderSymbols) { const token = body.tokens?.['1']?.find((entry: Record) => entry.symbol === symbol); expect(token).toMatchObject({ supplyProofProvenance: { source: 'deterministic-placeholder-address', status: 'non_reportable_until_erc20_deployed', }, }); expect(token.totalSupply).toBeUndefined(); expect(token.trackerCaveats).toEqual(expect.arrayContaining([expect.stringContaining('deterministic placeholder')])); } }); it('marks proofless base GRU c assets as proof gated instead of leaving silent supply fields', async () => { const res = await fetch(`${baseUrl}/api/v1/report/all?chainId=138`); expect(res.status).toBe(200); const body = (await res.json()) as Record; const cusdc = body.tokens?.['138']?.find((entry: Record) => entry.symbol === 'cUSDC'); expect(cusdc).toMatchObject({ type: 'base', registryFamily: 'iso4217', supplyProofProvenance: { source: 'missing-supply-proof', status: 'proof_required', }, }); expect(cusdc.totalSupply).toBeUndefined(); expect(cusdc.trackerCaveats).toEqual(expect.arrayContaining([expect.stringContaining('tracker-grade supply proof')])); }); }); describe('GET /api/v1/report/gas-registry', () => { it('returns both chain summaries and runtime pairs for gas rollout consumers', async () => { const res = await fetch(`${baseUrl}/api/v1/report/gas-registry`); expect(res.status).toBe(200); const body = (await res.json()) as Record; expect(body.gasAssetFamilies).toEqual( expect.arrayContaining([ expect.objectContaining({ familyKey: 'eth_mainnet', }), ]) ); expect(body.runtimePairs).toEqual( expect.arrayContaining([ expect.objectContaining({ key: '138-1-cETH-cWETH', familyKey: 'eth_mainnet', destinationChainId: 1, destinationChainName: 'Ethereum Mainnet', wrappedNativeQuoteSymbol: 'WETH', stableQuoteSymbol: 'USDC', }), ]) ); expect(body.chains).toEqual( expect.arrayContaining([ expect.objectContaining({ chainId: 1, }), ]) ); }); }); describe('GET /api/v1/report/adoption-readiness', () => { it('summarizes proved, proof-gated, pool-indexed, and scoring gates', async () => { const res = await fetch(`${baseUrl}/api/v1/report/adoption-readiness`); expect(res.status).toBe(200); const body = (await res.json()) as Record; expect(body.scope).toBe('gru-c-and-cw-assets'); expect(body.counts).toMatchObject({ candidates: expect.any(Number), reportableCandidates: expect.any(Number), nonReportablePlaceholder: expect.any(Number), proved: expect.any(Number), proofRequired: expect.any(Number), silent: 0, liquidityMissing: expect.any(Number), liquidityMissingWithPools: expect.any(Number), liquidityMissingWithoutPools: expect.any(Number), gruV2PoolsWithStatus: expect.any(Number), }); expect(body.institutional.score).toEqual(expect.any(Number)); expect(body.cryptoListing.score).toEqual(expect.any(Number)); expect(Array.isArray(body.blockerInventory.proofRequiredByChain)).toBe(true); expect(Array.isArray(body.blockerInventory.liquidityMissingByChain)).toBe(true); expect(Array.isArray(body.blockerInventory.liquidityMissingWithPoolsByChain)).toBe(true); expect(Array.isArray(body.blockerInventory.liquidityMissingWithoutPoolsByChain)).toBe(true); expect(Array.isArray(body.blockerInventory.liquidityMissingDetails)).toBe(true); expect(Array.isArray(body.blockerInventory.externalOfficialQuoteLiquidityByChain)).toBe(true); expect(Array.isArray(body.blockerInventory.nonReportablePlaceholderByChain)).toBe(true); expect(Array.isArray(body.blockerInventory.gruV2PoolsMissingStatus)).toBe(true); expect(Array.isArray(body.blockerInventory.notes)).toBe(true); }); it('does not treat external official USDC/USDT mirrors as GRU liquidity blockers', async () => { const res = await fetch(`${baseUrl}/api/v1/report/adoption-readiness`); expect(res.status).toBe(200); const body = (await res.json()) as Record; expect(body.counts.externalOfficialQuoteLiquidity).toBeGreaterThan(0); expect(body.blockerInventory.externalOfficialQuoteLiquidityByChain).toEqual( expect.arrayContaining([ expect.objectContaining({ chainId: 1, symbols: expect.arrayContaining(['cUSDC', 'cUSDT']), }), expect.objectContaining({ chainId: 56, symbols: expect.arrayContaining(['cUSDC', 'cUSDT']), }), ]) ); expect(body.blockerInventory.liquidityMissingDetails).not.toEqual( expect.arrayContaining([ expect.objectContaining({ chainId: 1, symbol: 'cUSDT' }), expect.objectContaining({ chainId: 56, symbol: 'cUSDC' }), ]) ); }); }); describe('GET /api/v1/report/token-list', () => { it('surfaces both V1 and V2 Chain 138 canonical GRU deployments explicitly', async () => { const res = await fetch(`${baseUrl}/api/v1/report/token-list?chainId=138`); expect(res.status).toBe(200); const body = (await res.json()) as Record; expect(body.tokens).toEqual( expect.arrayContaining([ expect.objectContaining({ symbol: 'cUSDT', chainId: 138, }), expect.objectContaining({ symbol: 'cUSDT_V2', chainId: 138, familySymbol: 'cUSDT', deploymentVersion: 'v2', preferredForX402: true, }), expect.objectContaining({ symbol: 'cUSDC', chainId: 138, }), expect.objectContaining({ symbol: 'cUSDC_V2', chainId: 138, familySymbol: 'cUSDC', deploymentVersion: 'v2', preferredForX402: true, }), ]) ); }); it('surfaces the cUSDW hub asset on Chain 138 and cWUSDW on active edge chains', async () => { const chain138Res = await fetch(`${baseUrl}/api/v1/report/token-list?chainId=138`); expect(chain138Res.status).toBe(200); const chain138Body = (await chain138Res.json()) as Record; expect(chain138Body.tokens).toEqual( expect.arrayContaining([ expect.objectContaining({ symbol: 'cUSDW', chainId: 138, }), ]) ); const bscRes = await fetch(`${baseUrl}/api/v1/report/token-list?chainId=56`); expect(bscRes.status).toBe(200); const bscBody = (await bscRes.json()) as Record; expect(bscBody.tokens).toEqual( expect.arrayContaining([ expect.objectContaining({ symbol: 'cWUSDW', chainId: 56, }), ]) ); }); it('surfaces cAUSDT on Chain 138 when configured and cWAUSDT on active edge chains', async () => { const chain138Res = await fetch(`${baseUrl}/api/v1/report/token-list?chainId=138`); expect(chain138Res.status).toBe(200); const chain138Body = (await chain138Res.json()) as Record; expect(chain138Body.tokens).toEqual( expect.arrayContaining([ expect.objectContaining({ symbol: 'cAUSDT', chainId: 138, }), ]) ); const bscRes = await fetch(`${baseUrl}/api/v1/report/token-list?chainId=56`); expect(bscRes.status).toBe(200); const bscBody = (await bscRes.json()) as Record; expect(bscBody.tokens).toEqual( expect.arrayContaining([ expect.objectContaining({ symbol: 'cWAUSDT', chainId: 56, }), ]) ); const polygonRes = await fetch(`${baseUrl}/api/v1/report/token-list?chainId=137`); expect(polygonRes.status).toBe(200); const polygonBody = (await polygonRes.json()) as Record; expect(polygonBody.tokens).toEqual( expect.arrayContaining([ expect.objectContaining({ symbol: 'cWAUSDT', chainId: 137, }), ]) ); const avalancheRes = await fetch(`${baseUrl}/api/v1/report/token-list?chainId=43114`); expect(avalancheRes.status).toBe(200); const avalancheBody = (await avalancheRes.json()) as Record; expect(avalancheBody.tokens).toEqual( expect.arrayContaining([ expect.objectContaining({ symbol: 'cWAUSDT', chainId: 43114, }), ]) ); const celoRes = await fetch(`${baseUrl}/api/v1/report/token-list?chainId=42220`); expect(celoRes.status).toBe(200); const celoBody = (await celoRes.json()) as Record; expect(celoBody.tokens).toEqual( expect.arrayContaining([ expect.objectContaining({ symbol: 'cWAUSDT', chainId: 42220, }), ]) ); }); it('surfaces cBTC on Chain 138 and cWBTC on the staged public mesh with monetary-unit metadata', async () => { const chain138Res = await fetch(`${baseUrl}/api/v1/report/token-list?chainId=138`); expect(chain138Res.status).toBe(200); const chain138Body = (await chain138Res.json()) as Record; expect(chain138Body.tokens).toEqual( expect.arrayContaining([ expect.objectContaining({ symbol: 'cBTC', chainId: 138, registryFamily: 'monetary_unit', }), ]) ); const mainnetRes = await fetch(`${baseUrl}/api/v1/report/token-list?chainId=1`); expect(mainnetRes.status).toBe(200); const mainnetBody = (await mainnetRes.json()) as Record; expect(mainnetBody.tokens).toEqual( expect.arrayContaining([ expect.objectContaining({ symbol: 'cWBTC', chainId: 1, registryFamily: 'monetary_unit', }), ]) ); }); it('surfaces gas-native canonicals on Chain 138 and mirrored cW gas tokens on their public lanes', async () => { const chain138Res = await fetch(`${baseUrl}/api/v1/report/token-list?chainId=138`); expect(chain138Res.status).toBe(200); const chain138Body = (await chain138Res.json()) as Record; expect(chain138Body.tokens).toEqual( expect.arrayContaining([ expect.objectContaining({ symbol: 'cETH', chainId: 138, registryFamily: 'gas_native', }), expect.objectContaining({ symbol: 'cETHL2', chainId: 138, registryFamily: 'gas_native', }), ]) ); const optimismRes = await fetch(`${baseUrl}/api/v1/report/token-list?chainId=10`); expect(optimismRes.status).toBe(200); const optimismBody = (await optimismRes.json()) as Record; expect(optimismBody.tokens).toEqual( expect.arrayContaining([ expect.objectContaining({ symbol: 'cWETHL2', chainId: 10, registryFamily: 'gas_native', }), ]) ); const mainnetRes = await fetch(`${baseUrl}/api/v1/report/token-list?chainId=1`); expect(mainnetRes.status).toBe(200); const mainnetBody = (await mainnetRes.json()) as Record; expect(mainnetBody.tokens).toEqual( expect.arrayContaining([ expect.objectContaining({ symbol: 'cWETH', chainId: 1, registryFamily: 'gas_native', }), ]) ); }); it('uses packaged DBIS-level local logo assets while preserving original logo references', async () => { const res = await fetch(`${baseUrl}/api/v1/report/token-list?chainId=1`); expect(res.status).toBe(200); const body = (await res.json()) as Record; const cwusdc = body.tokens.find((token: Record) => token.symbol === 'cWUSDC'); expect(cwusdc).toMatchObject({ logoURI: expect.stringMatching(/^https:\/\/127\.0\.0\.1:\d+\/api\/v1\/report\/logo\/cUSDC$/), originalLogoURI: expect.stringContaining('/token-lists/logos/gru/cUSDC.svg'), }); }); }); describe('GET /api/v1/report/cw-registry', () => { it('reads the live cW registry from deployment-status json when available', async () => { const previousPath = process.env.DEPLOYMENT_STATUS_JSON_PATH; const tempPath = `/tmp/token-aggregation-cw-registry-${Date.now()}.json`; process.env.DEPLOYMENT_STATUS_JSON_PATH = tempPath; await import('fs/promises').then((fs) => fs.writeFile( tempPath, JSON.stringify( { version: 'test-1', updated: '2026-04-04', chains: { '56': { name: 'BSC', cwTokens: { cWAUSDT: '0xe1a51Bc037a79AB36767561B147eb41780124934', }, }, }, }, null, 2 ) ) ); try { const res = await fetch(`${baseUrl}/api/v1/report/cw-registry?chainId=56`); expect(res.status).toBe(200); const body = (await res.json()) as Record; expect(body.source).toBe('deployment-status-file'); expect(body.complete).toBe(true); expect(body.version).toBe('test-1'); expect(body.chains).toEqual([ expect.objectContaining({ chainId: 56, name: 'BSC', tokens: [ expect.objectContaining({ symbol: 'cWAUSDT', }), ], }), ]); } finally { await import('fs/promises').then((fs) => fs.unlink(tempPath).catch(() => undefined)); if (previousPath === undefined) { delete process.env.DEPLOYMENT_STATUS_JSON_PATH; } else { process.env.DEPLOYMENT_STATUS_JSON_PATH = previousPath; } } }); }); describe('GET /api/v1/report/gru-v2-pmm-pools', () => { it('returns resolved PMM pools from deployment-status when file is set', async () => { const previousPath = process.env.DEPLOYMENT_STATUS_JSON_PATH; const tempPath = `/tmp/token-aggregation-gru-v2-pmm-${Date.now()}.json`; process.env.DEPLOYMENT_STATUS_JSON_PATH = tempPath; await import('fs/promises').then((fs) => fs.writeFile( tempPath, JSON.stringify( { version: 'test-gru-pools', updated: '2026-04-18', homeChainId: 138, chains: { '1': { name: 'Ethereum Mainnet', cwTokens: { cWUSDT: '0xaf5017d0163ecb99d9b5d94e3b4d7b09af44d8ae' }, anchorAddresses: { USDC: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48' }, pmmPools: [ { base: 'cWUSDT', quote: 'USDC', poolAddress: '0x1111111111111111111111111111111111111111', feeBps: 3, role: 'public_routing', publicRoutingEnabled: true, }, ], }, }, }, null, 2 ) ) ); try { const res = await fetch(`${baseUrl}/api/v1/report/gru-v2-pmm-pools?chainId=1`); expect(res.status).toBe(200); const body = (await res.json()) as Record; expect(body.source).toBe('deployment-status-file'); expect(body.complete).toBe(true); expect(body.version).toBe('test-gru-pools'); expect(Array.isArray(body.pools)).toBe(true); expect((body.pools as unknown[]).length).toBeGreaterThanOrEqual(1); expect((body.pools as Array<{ poolAddress: string }>)[0]).toMatchObject({ poolAddress: '0x1111111111111111111111111111111111111111', section: 'pmmPools', status: 'routing_enabled', statusReason: expect.stringContaining('public routing is enabled'), }); } finally { await import('fs/promises').then((fs) => fs.unlink(tempPath).catch(() => undefined)); if (previousPath === undefined) { delete process.env.DEPLOYMENT_STATUS_JSON_PATH; } else { process.env.DEPLOYMENT_STATUS_JSON_PATH = previousPath; } } }); }); describe('GET /api/v1/report/gas-registry', () => { it('reads the live gas rollout registry from deployment-status json when available', async () => { const res = await fetch(`${baseUrl}/api/v1/report/gas-registry?chainId=10`); expect(res.status).toBe(200); const body = (await res.json()) as Record; expect(body.source).toBe('deployment-status-file'); expect(body.gasAssetFamilies).toEqual( expect.arrayContaining([ expect.objectContaining({ familyKey: 'eth_l2', backingMode: 'hybrid_cap', }), ]) ); expect(body.chains).toEqual([ expect.objectContaining({ chainId: 10, families: [ expect.objectContaining({ familyKey: 'eth_l2', mirroredSymbol: 'cWETHL2', dodoPmm: expect.arrayContaining([ expect.objectContaining({ quote: 'WETH', }), ]), }), ], }), ]); }); }); });