import { describe, it, expect } from 'vitest'; import { runExactMatch, normalizeContractId, buildProviderIndex, runWildcardMatch, } from '../../../src/core/group/matching.js'; import type { StoredContract, MatchingConfig } from '../../../src/core/group/types.js'; describe('normalizeContractId', () => { it('lowercases HTTP method', () => { expect(normalizeContractId('http::get::/api/users')).toBe('http::GET::/api/users'); }); it('strips trailing slash from HTTP path', () => { expect(normalizeContractId('http::GET::/api/users/')).toBe('http::GET::/api/users'); }); it('lowercases gRPC package', () => { expect(normalizeContractId('grpc::Hr.UserService/GetUser')).toBe( 'grpc::hr.userservice/GetUser', ); }); it('lowercases thrift package and service while preserving method case', () => { expect(normalizeContractId('thrift::Billing.V1.OrderService/PlaceOrder')).toBe( 'thrift::billing.v1.orderservice/PlaceOrder', ); }); it('preserves case for malformed thrift id with leading slash', () => { expect(normalizeContractId('thrift::/PlaceOrder')).toBe('thrift::/PlaceOrder'); }); it('preserves case for malformed gRPC id with leading slash (no full-string lowercasing)', () => { expect(normalizeContractId('grpc::/MyPkg/DoThing')).toBe('grpc::/MyPkg/DoThing'); }); it('handles malformed grpc with leading slash and no package', () => { // grpc::/Method — leading slash, no package expect(normalizeContractId('grpc::/Method')).toBe('grpc::/Method'); }); it('handles grpc with no slash at all', () => { // grpc::ServiceName — no slash, ambiguous; MVP: lowercase entire token expect(normalizeContractId('grpc::ServiceName')).toBe('grpc::servicename'); }); it('trims and lowercases topic', () => { expect(normalizeContractId('topic:: Employee.Hired ')).toBe('topic::employee.hired'); }); it('lowercases lib package coordinates', () => { expect(normalizeContractId('lib::@Hr/Common::UserDTO')).toBe('lib::@hr/common::userdto'); }); }); describe('runExactMatch', () => { const makeContract = ( id: string, role: 'provider' | 'consumer', repo: string, ): StoredContract => ({ contractId: id, type: 'http', role, symbolUid: `uid-${repo}-${id}`, symbolRef: { filePath: `src/${repo}.ts`, name: `fn-${id}` }, symbolName: `fn-${id}`, confidence: 0.8, meta: {}, repo, }); it('matches provider and consumer with same contract ID', () => { const contracts: StoredContract[] = [ makeContract('http::GET::/api/users', 'provider', 'backend'), makeContract('http::GET::/api/users', 'consumer', 'frontend'), ]; const { matched, unmatched } = runExactMatch(contracts); expect(matched).toHaveLength(1); expect(matched[0].contractId).toBe('http::GET::/api/users'); expect(matched[0].matchType).toBe('exact'); expect(matched[0].confidence).toBe(1.0); expect(matched[0].from.repo).toBe('frontend'); expect(matched[0].to.repo).toBe('backend'); expect(unmatched).toHaveLength(0); }); it('handles multiple consumers for one provider', () => { const contracts: StoredContract[] = [ makeContract('http::GET::/api/users', 'provider', 'backend'), makeContract('http::GET::/api/users', 'consumer', 'frontend'), makeContract('http::GET::/api/users', 'consumer', 'bff'), ]; const { matched } = runExactMatch(contracts); expect(matched).toHaveLength(2); }); it('matches a specific-method consumer to a method-agnostic (wildcard) provider', () => { // A Django function view is method-agnostic (provider method '*'); a POST // consumer on the same path must still match it. const contracts: StoredContract[] = [ makeContract('http::*::/api/items', 'provider', 'backend'), makeContract('http::POST::/api/items', 'consumer', 'frontend'), ]; const { matched, unmatched } = runExactMatch(contracts); expect(matched).toHaveLength(1); expect(matched[0].from.repo).toBe('frontend'); expect(matched[0].to.repo).toBe('backend'); expect(unmatched).toHaveLength(0); }); it('reports unmatched contracts', () => { const contracts: StoredContract[] = [ makeContract('http::GET::/api/users', 'provider', 'backend'), makeContract('http::GET::/api/orphan', 'consumer', 'frontend'), ]; const { matched, unmatched } = runExactMatch(contracts); expect(matched).toHaveLength(0); expect(unmatched).toHaveLength(2); }); it('normalizes contract IDs before matching', () => { const contracts: StoredContract[] = [ makeContract('http::GET::/api/users/', 'provider', 'backend'), makeContract('http::get::/api/users', 'consumer', 'frontend'), ]; const { matched } = runExactMatch(contracts); expect(matched).toHaveLength(1); }); it('does not match contracts within the same repo', () => { const contracts: StoredContract[] = [ makeContract('http::GET::/api/users', 'provider', 'backend'), makeContract('http::GET::/api/users', 'consumer', 'backend'), ]; const { matched } = runExactMatch(contracts); expect(matched).toHaveLength(0); }); it('matches same-repo contracts with different service boundaries', () => { const contracts: StoredContract[] = [ { ...makeContract('http::GET::/api/users', 'provider', 'monorepo'), service: 'services/auth', }, { ...makeContract('http::GET::/api/users', 'consumer', 'monorepo'), service: 'services/gateway', }, ]; const { matched } = runExactMatch(contracts); expect(matched).toHaveLength(1); expect(matched[0].from.repo).toBe('monorepo'); expect(matched[0].to.repo).toBe('monorepo'); expect(matched[0].from.service).toBe('services/gateway'); expect(matched[0].to.service).toBe('services/auth'); }); it('does not match same-repo contracts with same service', () => { const contracts: StoredContract[] = [ { ...makeContract('http::GET::/api/users', 'provider', 'monorepo'), service: 'services/auth', }, { ...makeContract('http::GET::/api/users', 'consumer', 'monorepo'), service: 'services/auth', }, ]; const { matched } = runExactMatch(contracts); expect(matched).toHaveLength(0); }); it('suppresses configured GraphQL root fields from exact matching', () => { const provider = { ...makeContract('graphql::query::health', 'provider', 'api'), type: 'graphql' as const, }; const consumer = { ...makeContract('graphql::query::health', 'consumer', 'web'), type: 'graphql' as const, }; const { matched, unmatched } = runExactMatch([provider, consumer], undefined, { exclude_links_paths: ['/health'], }); expect(matched).toEqual([]); expect(unmatched).toEqual([]); }); it('does not match same-repo when only one has service', () => { const contracts: StoredContract[] = [ { ...makeContract('http::GET::/api/users', 'provider', 'monorepo'), service: 'services/auth', }, makeContract('http::GET::/api/users', 'consumer', 'monorepo'), ]; const { matched } = runExactMatch(contracts); expect(matched).toHaveLength(0); }); it('cross-repo matching works regardless of service field', () => { const contracts: StoredContract[] = [ { ...makeContract('http::GET::/api/users', 'provider', 'backend'), service: 'services/auth' }, { ...makeContract('http::GET::/api/users', 'consumer', 'frontend'), service: 'services/web' }, ]; const { matched } = runExactMatch(contracts); expect(matched).toHaveLength(1); expect(matched[0].from.service).toBe('services/web'); expect(matched[0].to.service).toBe('services/auth'); }); it('matches consumer http::*::path to a concrete provider method on that path', () => { const contracts: StoredContract[] = [ makeContract('http::POST::/api/users', 'provider', 'backend'), makeContract('http::*::/api/users', 'consumer', 'frontend'), ]; const { matched, unmatched } = runExactMatch(contracts); expect(matched).toHaveLength(1); expect(matched[0].contractId).toBe('http::*::/api/users'); expect(matched[0].to.repo).toBe('backend'); expect(unmatched).toHaveLength(0); }); }); // --------------------------------------------------------------------------- // Helpers for Task 6 tests // --------------------------------------------------------------------------- function makeGrpcContract( id: string, role: 'provider' | 'consumer', repo: string, overrides: Partial = {}, ): StoredContract { return { contractId: id, type: 'grpc', role, symbolUid: `uid-${repo}-${id}`, symbolRef: { filePath: `src/${repo}.ts`, name: `fn-${id}` }, symbolName: `fn-${id}`, confidence: 0.9, meta: {}, repo, ...overrides, }; } function makeThriftContract( id: string, role: 'provider' | 'consumer', repo: string, overrides: Partial = {}, ): StoredContract { return { contractId: id, type: 'thrift', role, symbolUid: `uid-${repo}-${id}`, symbolRef: { filePath: `src/${repo}.ts`, name: `fn-${id}` }, symbolName: `fn-${id}`, confidence: 0.9, meta: {}, repo, ...overrides, }; } // --------------------------------------------------------------------------- // buildProviderIndex // --------------------------------------------------------------------------- describe('buildProviderIndex', () => { it('test_buildProviderIndex_creates_normalized_keys', () => { const contracts: StoredContract[] = [ makeGrpcContract('grpc::Com.Example.UserService/GetUser', 'provider', 'backend'), makeGrpcContract('grpc::Com.Example.UserService/GetUser', 'consumer', 'frontend'), ]; const index = buildProviderIndex(contracts); // Only providers should be in the index expect(index.size).toBe(1); // Key should be normalized (lowercased package) expect(index.has('grpc::com.example.userservice/GetUser')).toBe(true); expect(index.get('grpc::com.example.userservice/GetUser')).toHaveLength(1); expect(index.get('grpc::com.example.userservice/GetUser')![0].role).toBe('provider'); }); }); // --------------------------------------------------------------------------- // runExactMatch — gRPC wildcard skip // --------------------------------------------------------------------------- describe('runExactMatch — gRPC wildcard handling', () => { it('test_runExactMatch_skips_grpc_wildcard_contracts', () => { const contracts: StoredContract[] = [ makeGrpcContract('grpc::com.example.UserService/*', 'consumer', 'frontend'), makeGrpcContract('grpc::com.example.UserService/*', 'provider', 'backend'), ]; const { matched, unmatched } = runExactMatch(contracts); // gRPC wildcards should NOT be matched in exact pass expect(matched).toHaveLength(0); // Both should appear in unmatched expect(unmatched).toHaveLength(2); }); it('test_runExactMatch_skips_thrift_wildcard_contracts', () => { const contracts: StoredContract[] = [ makeThriftContract('thrift::billing.v1.OrderService/*', 'consumer', 'frontend'), makeThriftContract('thrift::billing.v1.OrderService/*', 'provider', 'backend'), ]; const { matched, unmatched } = runExactMatch(contracts); expect(matched).toHaveLength(0); expect(unmatched).toHaveLength(2); }); it('test_runExactMatch_does_not_skip_http_wildcards', () => { const contracts: StoredContract[] = [ { contractId: 'http::GET::/api/users', type: 'http', role: 'provider', symbolUid: 'uid-backend-http', symbolRef: { filePath: 'src/backend.ts', name: 'fn-http' }, symbolName: 'fn-http', confidence: 0.9, meta: {}, repo: 'backend', }, { contractId: 'http::*::/api/users', type: 'http', role: 'consumer', symbolUid: 'uid-frontend-http', symbolRef: { filePath: 'src/frontend.ts', name: 'fn-http' }, symbolName: 'fn-http', confidence: 0.9, meta: {}, repo: 'frontend', }, ]; const { matched } = runExactMatch(contracts); // HTTP wildcard should still match via findMatchingKeys expect(matched).toHaveLength(1); expect(matched[0].contractId).toBe('http::*::/api/users'); }); }); // --------------------------------------------------------------------------- // runWildcardMatch // --------------------------------------------------------------------------- describe('runWildcardMatch', () => { it('test_runWildcardMatch_fq_service_match', () => { const consumer = makeGrpcContract('grpc::com.example.UserService/*', 'consumer', 'frontend'); const provider = makeGrpcContract( 'grpc::com.example.UserService/GetUser', 'provider', 'backend', ); const providerIndex = buildProviderIndex([provider]); const { matched } = runWildcardMatch([consumer], providerIndex); expect(matched).toHaveLength(1); expect(matched[0].from.repo).toBe('frontend'); expect(matched[0].to.repo).toBe('backend'); }); it('test_runWildcardMatch_bare_name_match', () => { const consumer = makeGrpcContract('grpc::UserService/*', 'consumer', 'frontend'); const provider = makeGrpcContract( 'grpc::com.example.UserService/GetUser', 'provider', 'backend', ); const providerIndex = buildProviderIndex([provider]); const { matched } = runWildcardMatch([consumer], providerIndex); expect(matched).toHaveLength(1); expect(matched[0].from.repo).toBe('frontend'); expect(matched[0].to.repo).toBe('backend'); }); it('test_runWildcardMatch_no_match_different_service', () => { const consumer = makeGrpcContract('grpc::UserService/*', 'consumer', 'frontend'); const provider = makeGrpcContract( 'grpc::com.example.OtherService/GetUser', 'provider', 'backend', ); const providerIndex = buildProviderIndex([provider]); const { matched, remaining } = runWildcardMatch([consumer], providerIndex); expect(matched).toHaveLength(0); expect(remaining).toContainEqual(consumer); }); it('test_runWildcardMatch_skips_wildcard_providers', () => { const consumer = makeGrpcContract('grpc::com.example.UserService/*', 'consumer', 'frontend'); const provider = makeGrpcContract('grpc::com.example.UserService/*', 'provider', 'backend'); const providerIndex = buildProviderIndex([provider]); const { matched } = runWildcardMatch([consumer], providerIndex); // Wildcard provider key ends with /*, so it should be skipped expect(matched).toHaveLength(0); }); it('test_runWildcardMatch_confidence_min', () => { const consumer = makeGrpcContract('grpc::com.example.UserService/*', 'consumer', 'frontend', { confidence: 0.7, }); const provider = makeGrpcContract( 'grpc::com.example.UserService/GetUser', 'provider', 'backend', { confidence: 0.5, }, ); const providerIndex = buildProviderIndex([provider]); const { matched } = runWildcardMatch([consumer], providerIndex); expect(matched).toHaveLength(1); expect(matched[0].confidence).toBe(0.5); }); it('test_runWildcardMatch_matchType_wildcard', () => { const consumer = makeGrpcContract('grpc::com.example.UserService/*', 'consumer', 'frontend'); const provider = makeGrpcContract( 'grpc::com.example.UserService/GetUser', 'provider', 'backend', ); const providerIndex = buildProviderIndex([provider]); const { matched } = runWildcardMatch([consumer], providerIndex); expect(matched).toHaveLength(1); expect(matched[0].matchType).toBe('wildcard'); }); it('test_runWildcardMatch_contractId_is_consumers', () => { const consumer = makeGrpcContract('grpc::com.example.UserService/*', 'consumer', 'frontend'); const provider = makeGrpcContract( 'grpc::com.example.UserService/GetUser', 'provider', 'backend', ); const providerIndex = buildProviderIndex([provider]); const { matched } = runWildcardMatch([consumer], providerIndex); expect(matched).toHaveLength(1); expect(matched[0].contractId).toBe('grpc::com.example.UserService/*'); }); it('matches thrift fully-qualified service wildcard to a thrift provider method', () => { const consumer = makeThriftContract( 'thrift::billing.v1.OrderService/*', 'consumer', 'frontend', ); const provider = makeThriftContract( 'thrift::billing.v1.OrderService/PlaceOrder', 'provider', 'backend', ); const providerIndex = buildProviderIndex([provider]); const { matched, remaining } = runWildcardMatch([consumer], providerIndex); expect(matched).toHaveLength(1); expect(matched[0].type).toBe('thrift'); expect(matched[0].from.repo).toBe('frontend'); expect(matched[0].to.repo).toBe('backend'); expect(remaining).toHaveLength(0); }); it('matches bare thrift service wildcard to a package-qualified thrift provider', () => { const consumer = makeThriftContract('thrift::OrderService/*', 'consumer', 'frontend'); const provider = makeThriftContract( 'thrift::billing.v1.OrderService/PlaceOrder', 'provider', 'backend', ); const providerIndex = buildProviderIndex([provider]); const { matched } = runWildcardMatch([consumer], providerIndex); expect(matched).toHaveLength(1); expect(matched[0].contractId).toBe('thrift::OrderService/*'); }); it('does not match bare thrift service wildcard when multiple package-qualified services match', () => { const consumer = makeThriftContract('thrift::OrderService/*', 'consumer', 'frontend'); const billingProvider = makeThriftContract( 'thrift::billing.v1.OrderService/PlaceOrder', 'provider', 'billing', ); const salesProvider = makeThriftContract( 'thrift::sales.v1.OrderService/PlaceOrder', 'provider', 'sales', ); const providerIndex = buildProviderIndex([billingProvider, salesProvider]); const { matched, remaining } = runWildcardMatch([consumer], providerIndex); expect(matched).toHaveLength(0); expect(remaining).toEqual([consumer]); }); it('keeps fully-qualified thrift service wildcard matching when same bare service appears elsewhere', () => { const consumer = makeThriftContract( 'thrift::billing.v1.OrderService/*', 'consumer', 'frontend', ); const billingProvider = makeThriftContract( 'thrift::billing.v1.OrderService/PlaceOrder', 'provider', 'billing', ); const salesProvider = makeThriftContract( 'thrift::sales.v1.OrderService/PlaceOrder', 'provider', 'sales', ); const providerIndex = buildProviderIndex([billingProvider, salesProvider]); const { matched, remaining } = runWildcardMatch([consumer], providerIndex); expect(matched).toHaveLength(1); expect(matched[0].to.repo).toBe('billing'); expect(remaining).toHaveLength(0); }); it('matches bare thrift service method to a package-qualified thrift provider method', () => { const consumer = makeThriftContract('thrift::OrderService/PlaceOrder', 'consumer', 'frontend'); const provider = makeThriftContract( 'thrift::billing.v1.OrderService/PlaceOrder', 'provider', 'backend', ); const providerIndex = buildProviderIndex([provider]); const { matched, unmatched } = runExactMatch([consumer, provider], providerIndex); expect(matched).toHaveLength(1); expect(matched[0].type).toBe('thrift'); expect(matched[0].matchType).toBe('exact'); expect(matched[0].contractId).toBe('thrift::OrderService/PlaceOrder'); expect(matched[0].from.repo).toBe('frontend'); expect(matched[0].to.repo).toBe('backend'); expect(unmatched).toHaveLength(0); }); it('does not match bare thrift service method to a different provider method', () => { const consumer = makeThriftContract('thrift::OrderService/PlaceOrder', 'consumer', 'frontend'); const provider = makeThriftContract( 'thrift::billing.v1.OrderService/GetOrderStatus', 'provider', 'backend', ); const providerIndex = buildProviderIndex([provider]); const { matched, unmatched } = runExactMatch([consumer, provider], providerIndex); expect(matched).toHaveLength(0); expect(unmatched).toEqual([consumer, provider]); }); it('does not match bare thrift service method when multiple package-qualified providers match', () => { const consumer = makeThriftContract('thrift::OrderService/PlaceOrder', 'consumer', 'frontend'); const billingProvider = makeThriftContract( 'thrift::billing.v1.OrderService/PlaceOrder', 'provider', 'billing', ); const salesProvider = makeThriftContract( 'thrift::sales.v1.OrderService/PlaceOrder', 'provider', 'sales', ); const providerIndex = buildProviderIndex([salesProvider, billingProvider]); const { matched, unmatched } = runExactMatch( [consumer, salesProvider, billingProvider], providerIndex, ); expect(matched).toHaveLength(0); expect(unmatched).toEqual([consumer, salesProvider, billingProvider]); }); it('does not match a thrift wildcard to a gRPC provider', () => { const consumer = makeThriftContract('thrift::OrderService/*', 'consumer', 'frontend'); const provider = makeGrpcContract( 'grpc::billing.v1.OrderService/PlaceOrder', 'provider', 'backend', ); const providerIndex = buildProviderIndex([provider]); const { matched, remaining } = runWildcardMatch([consumer], providerIndex); expect(matched).toHaveLength(0); expect(remaining).toEqual([consumer]); }); }); describe('buildNoisyContractFilter (via runExactMatch)', () => { const makeContract = ( id: string, role: 'provider' | 'consumer', repo: string, ): StoredContract => ({ contractId: id, type: 'http', role, symbolUid: `uid-${repo}-${id}`, symbolRef: { filePath: `src/${repo}.ts`, name: `fn-${id}` }, symbolName: `fn-${id}`, confidence: 0.8, meta: {}, repo, }); it('exclude_links_paths prevents cross-links for configured paths', () => { const matchingConfig: MatchingConfig = { exclude_links_paths: ['/ping'], exclude_links_param_only_paths: false, }; const contracts: StoredContract[] = [ makeContract('http::GET::/ping', 'provider', 'backend'), makeContract('http::GET::/ping', 'consumer', 'frontend'), makeContract('http::GET::/api/users', 'provider', 'backend'), makeContract('http::GET::/api/users', 'consumer', 'frontend'), ]; const providerIndex = buildProviderIndex(contracts, matchingConfig); const { matched, unmatched } = runExactMatch(contracts, providerIndex, matchingConfig); expect(matched).toHaveLength(1); expect(matched[0].contractId).toBe('http::GET::/api/users'); }); it('excluded providers do not appear in matched', () => { const matchingConfig: MatchingConfig = { exclude_links_paths: ['/health'], exclude_links_param_only_paths: false, }; const contracts: StoredContract[] = [ makeContract('http::GET::/health', 'provider', 'backend'), makeContract('http::GET::/health', 'consumer', 'frontend'), ]; const providerIndex = buildProviderIndex(contracts, matchingConfig); const { matched } = runExactMatch(contracts, providerIndex, matchingConfig); expect(matched).toHaveLength(0); }); it('excluded contracts do not appear in unmatched', () => { const matchingConfig: MatchingConfig = { exclude_links_paths: ['/ping'], exclude_links_param_only_paths: false, }; const contracts: StoredContract[] = [ makeContract('http::GET::/ping', 'provider', 'backend'), makeContract('http::GET::/ping', 'consumer', 'frontend'), ]; const providerIndex = buildProviderIndex(contracts, matchingConfig); const { matched, unmatched } = runExactMatch(contracts, providerIndex, matchingConfig); expect(matched).toHaveLength(0); expect(unmatched).toHaveLength(0); }); it('exclude_links_param_only_paths filters /{param} and /{param}/{param}', () => { const matchingConfig: MatchingConfig = { exclude_links_paths: [], exclude_links_param_only_paths: true, }; const contracts: StoredContract[] = [ makeContract('http::GET::/{param}', 'provider', 'backend'), makeContract('http::GET::/{param}', 'consumer', 'frontend'), makeContract('http::GET::/{param}/{param}', 'provider', 'backend'), makeContract('http::GET::/{param}/{param}', 'consumer', 'frontend'), ]; const providerIndex = buildProviderIndex(contracts, matchingConfig); const { matched, unmatched } = runExactMatch(contracts, providerIndex, matchingConfig); expect(matched).toHaveLength(0); expect(unmatched).toHaveLength(0); }); it('mixed routes like /users/{param} are NOT excluded by param_only', () => { const matchingConfig: MatchingConfig = { exclude_links_paths: [], exclude_links_param_only_paths: true, }; const contracts: StoredContract[] = [ makeContract('http::GET::/users/{param}', 'provider', 'backend'), makeContract('http::GET::/users/{param}', 'consumer', 'frontend'), ]; const providerIndex = buildProviderIndex(contracts, matchingConfig); const { matched } = runExactMatch(contracts, providerIndex, matchingConfig); expect(matched).toHaveLength(1); expect(matched[0].contractId).toBe('http::GET::/users/{param}'); }); it('default config (no exclusions) produces no filtering', () => { const contracts: StoredContract[] = [ makeContract('http::GET::/ping', 'provider', 'backend'), makeContract('http::GET::/ping', 'consumer', 'frontend'), makeContract('http::GET::/{param}', 'provider', 'backend'), makeContract('http::GET::/{param}', 'consumer', 'frontend'), ]; const { matched } = runExactMatch(contracts); expect(matched).toHaveLength(2); }); it('trailing slash on contractId still matches configured exclusion', () => { const matchingConfig: MatchingConfig = { exclude_links_paths: ['/ping'], exclude_links_param_only_paths: false, }; const contracts: StoredContract[] = [ makeContract('http::GET::/ping/', 'provider', 'backend'), makeContract('http::GET::/ping/', 'consumer', 'frontend'), ]; const providerIndex = buildProviderIndex(contracts, matchingConfig); const { matched, unmatched } = runExactMatch(contracts, providerIndex, matchingConfig); expect(matched).toHaveLength(0); expect(unmatched).toHaveLength(0); }); it('root path exclusion ["/"] suppresses http::GET::/ contracts', () => { const matchingConfig: MatchingConfig = { exclude_links_paths: ['/'], exclude_links_param_only_paths: false, }; const contracts: StoredContract[] = [ makeContract('http::GET::/', 'provider', 'backend'), makeContract('http::GET::/', 'consumer', 'frontend'), makeContract('http::GET::/api/users', 'provider', 'backend'), makeContract('http::GET::/api/users', 'consumer', 'frontend'), ]; const providerIndex = buildProviderIndex(contracts, matchingConfig); const { matched, unmatched } = runExactMatch(contracts, providerIndex, matchingConfig); expect(matched).toHaveLength(1); expect(matched[0].contractId).toBe('http::GET::/api/users'); expect(unmatched).toHaveLength(0); }); it('non-HTTP contracts are never filtered', () => { const matchingConfig: MatchingConfig = { exclude_links_paths: ['/ping'], exclude_links_param_only_paths: true, }; const contracts: StoredContract[] = [ { contractId: 'topic::events.ping', type: 'topic', role: 'provider', symbolUid: 'uid-backend-topic', symbolRef: { filePath: 'src/backend.ts', name: 'fn-topic' }, symbolName: 'fn-topic', confidence: 0.8, meta: {}, repo: 'backend', }, { contractId: 'topic::events.ping', type: 'topic', role: 'consumer', symbolUid: 'uid-frontend-topic', symbolRef: { filePath: 'src/frontend.ts', name: 'fn-topic' }, symbolName: 'fn-topic', confidence: 0.8, meta: {}, repo: 'frontend', }, ]; const providerIndex = buildProviderIndex(contracts, matchingConfig); const { matched } = runExactMatch(contracts, providerIndex, matchingConfig); expect(matched).toHaveLength(1); }); });