import { describe, it, expect, beforeEach, afterEach } from 'vitest'; import fsp from 'node:fs/promises'; import path from 'node:path'; import os from 'node:os'; import { cleanupTempDir } from '../../helpers/test-db.js'; import { writeBridge, openBridgeDbReadOnly, queryBridge, closeBridgeDb, } from '../../../src/core/group/bridge-db.js'; import type { CrossLink } from '../../../src/core/group/types.js'; import { makeContract } from './fixtures.js'; /** * See bridge-db.test.ts header for context: LadybugDB 0.16.0 cannot release * Windows file locks until process exit, so any close-then-reopen pattern * (writeBridge → openBridgeDbReadOnly) raises Win32 Error 33 in-process. * Production paths are unaffected (single open per process). */ const itLbugReopen = process.platform === 'win32' ? it.skip : it; describe('bridge-db edge cases', () => { let tmpDir: string; beforeEach(async () => { tmpDir = await fsp.mkdtemp(path.join(os.tmpdir(), 'bridge-edge-')); }); afterEach(async () => { await cleanupTempDir(tmpDir); }); it('test_openBridgeDbReadOnly_version_gate_returns_null_for_incompatible', async () => { // Create a dummy bridge.lbug file so the access check passes await fsp.writeFile(path.join(tmpDir, 'bridge.lbug'), 'dummy'); // Write meta.json with an incompatible version (999) await fsp.writeFile( path.join(tmpDir, 'meta.json'), JSON.stringify({ version: 999, generatedAt: '', missingRepos: [] }), ); const handle = await openBridgeDbReadOnly(tmpDir); expect(handle).toBeNull(); }); itLbugReopen('test_openBridgeDbReadOnly_bak_recovery_restores_bridge', async () => { // Write a valid bridge await writeBridge(tmpDir, { contracts: [makeContract()], crossLinks: [], repoSnapshots: {}, missingRepos: [], }); // Move bridge.lbug → bridge.lbug.bak (simulating interrupted swap) const dbPath = path.join(tmpDir, 'bridge.lbug'); const bakPath = path.join(tmpDir, 'bridge.lbug.bak'); await fsp.rename(dbPath, bakPath); // openBridgeDbReadOnly should auto-recover from .bak const handle = await openBridgeDbReadOnly(tmpDir); expect(handle).not.toBeNull(); const rows = await queryBridge<{ repo: string }>( handle!, 'MATCH (c:Contract) RETURN c.repo AS repo', ); expect(rows).toHaveLength(1); await closeBridgeDb(handle!); }); itLbugReopen('test_writeBridge_crossLink_with_missing_to_node_silently_skipped', async () => { const provider = makeContract({ repo: 'backend', role: 'provider' }); const consumer = makeContract({ repo: 'frontend', role: 'consumer', symbolRef: { filePath: 'src/api.ts', name: 'fetchUsers' }, symbolName: 'fetchUsers', }); // CrossLink referencing a 'to' endpoint that doesn't match any contract node const link: CrossLink = { from: { repo: 'frontend', symbolUid: '', symbolRef: { filePath: 'src/api.ts', name: 'fetchUsers' }, }, to: { repo: 'nonexistent-repo', symbolUid: 'uid-missing', symbolRef: { filePath: 'src/missing.ts', name: 'missingFn' }, }, type: 'http', contractId: 'http::GET::/api/users', matchType: 'exact', confidence: 1.0, }; // Should not throw — the link is silently skipped await writeBridge(tmpDir, { contracts: [provider, consumer], crossLinks: [link], repoSnapshots: {}, missingRepos: [], }); const handle = await openBridgeDbReadOnly(tmpDir); expect(handle).not.toBeNull(); // No cross-links should exist since 'to' node was missing const rows = await queryBridge<{ matchType: string }>( handle!, 'MATCH (a:Contract)-[l:ContractLink]->(b:Contract) RETURN l.matchType AS matchType', ); expect(rows).toHaveLength(0); // But contracts should still be present const contractRows = await queryBridge<{ repo: string }>( handle!, 'MATCH (c:Contract) RETURN c.repo AS repo', ); expect(contractRows).toHaveLength(2); await closeBridgeDb(handle!); }); itLbugReopen( 'test_writeBridge_manifest_grpc_link_with_symbol_uids_persists_queryable_contract_edge', async () => { const provider = makeContract({ contractId: 'grpc::auth.AuthService/Login', type: 'grpc', role: 'provider', repo: 'platform/auth', symbolUid: 'uid-auth-login', symbolRef: { filePath: 'src/auth.proto', name: 'Login' }, symbolName: 'auth.AuthService/Login', }); const consumer = makeContract({ contractId: 'grpc::auth.AuthService/Login', type: 'grpc', role: 'consumer', repo: 'platform/orders', symbolUid: 'uid-orders-client', symbolRef: { filePath: 'src/client.ts', name: 'AuthServiceClient' }, symbolName: 'auth.AuthService/Login', }); const link: CrossLink = { from: { repo: 'platform/orders', symbolUid: 'uid-orders-client', symbolRef: { filePath: 'src/client.ts', name: 'AuthServiceClient' }, }, to: { repo: 'platform/auth', symbolUid: 'uid-auth-login', symbolRef: { filePath: 'src/auth.proto', name: 'Login' }, }, type: 'grpc', contractId: 'grpc::auth.AuthService/Login', matchType: 'manifest', confidence: 1.0, }; await writeBridge(tmpDir, { contracts: [provider, consumer], crossLinks: [link], repoSnapshots: {}, missingRepos: [], }); const handle = await openBridgeDbReadOnly(tmpDir); expect(handle).not.toBeNull(); const rows = await queryBridge<{ contractId: string; matchType: string; fromRepo: string; toRepo: string; }>( handle!, `MATCH (a:Contract)-[l:ContractLink]->(b:Contract) RETURN l.contractId AS contractId, l.matchType AS matchType, l.fromRepo AS fromRepo, l.toRepo AS toRepo`, ); expect(rows).toEqual([ { contractId: 'grpc::auth.AuthService/Login', matchType: 'manifest', fromRepo: 'platform/orders', toRepo: 'platform/auth', }, ]); await closeBridgeDb(handle!); }, ); });