import { expect, tap } from '@git.zone/tstest/tapbundle'; import { ReferenceResolver } from '../ts/config/classes.reference-resolver.js'; import type { ISecurityProfile, INetworkTarget, IRouteMetadata } from '../ts_interfaces/data/route-management.js'; import type { IRouteConfig } from '@push.rocks/smartproxy'; // ============================================================================ // Helpers: access private maps for direct unit testing without DB // ============================================================================ function injectProfile(resolver: ReferenceResolver, profile: ISecurityProfile): void { (resolver as any).profiles.set(profile.id, profile); } function injectTarget(resolver: ReferenceResolver, target: INetworkTarget): void { (resolver as any).targets.set(target.id, target); } function makeProfile(overrides: Partial = {}): ISecurityProfile { return { id: 'profile-1', name: 'STANDARD', description: 'Test profile', security: { ipAllowList: ['192.168.0.0/16', '10.0.0.0/8'], maxConnections: 1000, }, createdAt: Date.now(), updatedAt: Date.now(), createdBy: 'test', ...overrides, }; } function makeTarget(overrides: Partial = {}): INetworkTarget { return { id: 'target-1', name: 'INFRA', description: 'Test target', host: '192.168.5.247', port: 443, createdAt: Date.now(), updatedAt: Date.now(), createdBy: 'test', ...overrides, }; } function makeRoute(overrides: Partial = {}): IRouteConfig { return { name: 'test-route', match: { ports: 443, domains: 'test.example.com' }, action: { type: 'forward', targets: [{ host: 'placeholder', port: 80 }] }, ...overrides, } as IRouteConfig; } // ============================================================================ // Resolution tests // ============================================================================ let resolver: ReferenceResolver; tap.test('should create ReferenceResolver instance', async () => { resolver = new ReferenceResolver(); expect(resolver).toBeTruthy(); }); tap.test('should list empty profiles and targets initially', async () => { expect(resolver.listProfiles()).toBeArray(); expect(resolver.listProfiles().length).toEqual(0); expect(resolver.listTargets()).toBeArray(); expect(resolver.listTargets().length).toEqual(0); }); // ---- Security profile resolution ---- tap.test('should resolve security profile onto a route', async () => { const profile = makeProfile(); injectProfile(resolver, profile); const route = makeRoute(); const metadata: IRouteMetadata = { securityProfileRef: 'profile-1' }; const result = resolver.resolveRoute(route, metadata); expect(result.route.security).toBeTruthy(); expect(result.route.security!.ipAllowList).toContain('192.168.0.0/16'); expect(result.route.security!.ipAllowList).toContain('10.0.0.0/8'); expect(result.route.security!.maxConnections).toEqual(1000); expect(result.metadata.securityProfileName).toEqual('STANDARD'); expect(result.metadata.lastResolvedAt).toBeTruthy(); }); tap.test('should merge inline route security with profile security', async () => { const route = makeRoute({ security: { ipAllowList: ['127.0.0.1'], maxConnections: 5000, }, }); const metadata: IRouteMetadata = { securityProfileRef: 'profile-1' }; const result = resolver.resolveRoute(route, metadata); // IP lists are unioned expect(result.route.security!.ipAllowList).toContain('192.168.0.0/16'); expect(result.route.security!.ipAllowList).toContain('10.0.0.0/8'); expect(result.route.security!.ipAllowList).toContain('127.0.0.1'); // Inline maxConnections overrides profile expect(result.route.security!.maxConnections).toEqual(5000); }); tap.test('should deduplicate IP lists during merge', async () => { const route = makeRoute({ security: { ipAllowList: ['192.168.0.0/16', '127.0.0.1'], }, }); const metadata: IRouteMetadata = { securityProfileRef: 'profile-1' }; const result = resolver.resolveRoute(route, metadata); // 192.168.0.0/16 appears in both profile and route, should be deduplicated const count = result.route.security!.ipAllowList!.filter(ip => ip === '192.168.0.0/16').length; expect(count).toEqual(1); }); tap.test('should handle missing profile gracefully', async () => { const route = makeRoute(); const metadata: IRouteMetadata = { securityProfileRef: 'nonexistent-profile' }; const result = resolver.resolveRoute(route, metadata); // Route should be unchanged expect(result.route.security).toBeUndefined(); expect(result.metadata.securityProfileName).toBeUndefined(); }); // ---- Profile inheritance ---- tap.test('should resolve profile inheritance (extendsProfiles)', async () => { const baseProfile = makeProfile({ id: 'base-profile', name: 'BASE', security: { ipAllowList: ['10.0.0.0/8'], maxConnections: 500, }, }); injectProfile(resolver, baseProfile); const extendedProfile = makeProfile({ id: 'extended-profile', name: 'EXTENDED', security: { ipAllowList: ['160.79.104.0/21'], }, extendsProfiles: ['base-profile'], }); injectProfile(resolver, extendedProfile); const route = makeRoute(); const metadata: IRouteMetadata = { securityProfileRef: 'extended-profile' }; const result = resolver.resolveRoute(route, metadata); // Should have IPs from both base and extended profiles expect(result.route.security!.ipAllowList).toContain('10.0.0.0/8'); expect(result.route.security!.ipAllowList).toContain('160.79.104.0/21'); // maxConnections from base (extended doesn't override) expect(result.route.security!.maxConnections).toEqual(500); expect(result.metadata.securityProfileName).toEqual('EXTENDED'); }); tap.test('should detect circular profile inheritance', async () => { const profileA = makeProfile({ id: 'circular-a', name: 'A', security: { ipAllowList: ['1.1.1.1'] }, extendsProfiles: ['circular-b'], }); const profileB = makeProfile({ id: 'circular-b', name: 'B', security: { ipAllowList: ['2.2.2.2'] }, extendsProfiles: ['circular-a'], }); injectProfile(resolver, profileA); injectProfile(resolver, profileB); const route = makeRoute(); const metadata: IRouteMetadata = { securityProfileRef: 'circular-a' }; // Should not infinite loop — resolves what it can const result = resolver.resolveRoute(route, metadata); expect(result.route.security).toBeTruthy(); expect(result.route.security!.ipAllowList).toContain('1.1.1.1'); }); // ---- Network target resolution ---- tap.test('should resolve network target onto a route', async () => { const target = makeTarget(); injectTarget(resolver, target); const route = makeRoute(); const metadata: IRouteMetadata = { networkTargetRef: 'target-1' }; const result = resolver.resolveRoute(route, metadata); expect(result.route.action.targets).toBeTruthy(); expect(result.route.action.targets![0].host).toEqual('192.168.5.247'); expect(result.route.action.targets![0].port).toEqual(443); expect(result.metadata.networkTargetName).toEqual('INFRA'); expect(result.metadata.lastResolvedAt).toBeTruthy(); }); tap.test('should handle missing target gracefully', async () => { const route = makeRoute(); const metadata: IRouteMetadata = { networkTargetRef: 'nonexistent-target' }; const result = resolver.resolveRoute(route, metadata); // Route targets should be unchanged (still the placeholder) expect(result.route.action.targets![0].host).toEqual('placeholder'); expect(result.metadata.networkTargetName).toBeUndefined(); }); // ---- Combined resolution ---- tap.test('should resolve both profile and target simultaneously', async () => { const route = makeRoute(); const metadata: IRouteMetadata = { securityProfileRef: 'profile-1', networkTargetRef: 'target-1', }; const result = resolver.resolveRoute(route, metadata); // Security from profile expect(result.route.security!.ipAllowList).toContain('192.168.0.0/16'); expect(result.route.security!.maxConnections).toEqual(1000); // Target from network target expect(result.route.action.targets![0].host).toEqual('192.168.5.247'); expect(result.route.action.targets![0].port).toEqual(443); // Both names recorded expect(result.metadata.securityProfileName).toEqual('STANDARD'); expect(result.metadata.networkTargetName).toEqual('INFRA'); }); tap.test('should skip resolution when no metadata refs', async () => { const route = makeRoute({ security: { ipAllowList: ['1.2.3.4'] }, }); const metadata: IRouteMetadata = {}; const result = resolver.resolveRoute(route, metadata); // Route should be completely unchanged expect(result.route.security!.ipAllowList).toContain('1.2.3.4'); expect(result.route.security!.ipAllowList!.length).toEqual(1); expect(result.route.action.targets![0].host).toEqual('placeholder'); }); tap.test('should be idempotent — resolving twice gives same result', async () => { const route = makeRoute(); const metadata: IRouteMetadata = { securityProfileRef: 'profile-1', networkTargetRef: 'target-1', }; const first = resolver.resolveRoute(route, metadata); const second = resolver.resolveRoute(first.route, first.metadata); expect(second.route.security!.ipAllowList!.length).toEqual(first.route.security!.ipAllowList!.length); expect(second.route.action.targets![0].host).toEqual(first.route.action.targets![0].host); expect(second.route.action.targets![0].port).toEqual(first.route.action.targets![0].port); }); // ---- Lookup helpers ---- tap.test('should find routes by profile ref (sync)', async () => { const storedRoutes = new Map(); storedRoutes.set('route-a', { id: 'route-a', route: makeRoute({ name: 'route-a' }), enabled: true, metadata: { securityProfileRef: 'profile-1' }, }); storedRoutes.set('route-b', { id: 'route-b', route: makeRoute({ name: 'route-b' }), enabled: true, metadata: { networkTargetRef: 'target-1' }, }); storedRoutes.set('route-c', { id: 'route-c', route: makeRoute({ name: 'route-c' }), enabled: true, metadata: { securityProfileRef: 'profile-1', networkTargetRef: 'target-1' }, }); const profileRefs = resolver.findRoutesByProfileRefSync('profile-1', storedRoutes); expect(profileRefs.length).toEqual(2); expect(profileRefs).toContain('route-a'); expect(profileRefs).toContain('route-c'); const targetRefs = resolver.findRoutesByTargetRefSync('target-1', storedRoutes); expect(targetRefs.length).toEqual(2); expect(targetRefs).toContain('route-b'); expect(targetRefs).toContain('route-c'); }); tap.test('should get profile usage for a specific profile ID', async () => { const storedRoutes = new Map(); storedRoutes.set('route-x', { id: 'route-x', route: makeRoute({ name: 'my-route' }), enabled: true, metadata: { securityProfileRef: 'profile-1' }, }); const usage = resolver.getProfileUsageForId('profile-1', storedRoutes); expect(usage.length).toEqual(1); expect(usage[0].id).toEqual('route-x'); expect(usage[0].routeName).toEqual('my-route'); }); tap.test('should get target usage for a specific target ID', async () => { const storedRoutes = new Map(); storedRoutes.set('route-y', { id: 'route-y', route: makeRoute({ name: 'other-route' }), enabled: true, metadata: { networkTargetRef: 'target-1' }, }); const usage = resolver.getTargetUsageForId('target-1', storedRoutes); expect(usage.length).toEqual(1); expect(usage[0].id).toEqual('route-y'); expect(usage[0].routeName).toEqual('other-route'); }); // ---- Profile/target getters ---- tap.test('should get profile by name', async () => { const profile = resolver.getProfileByName('STANDARD'); expect(profile).toBeTruthy(); expect(profile!.id).toEqual('profile-1'); }); tap.test('should get target by name', async () => { const target = resolver.getTargetByName('INFRA'); expect(target).toBeTruthy(); expect(target!.id).toEqual('target-1'); }); tap.test('should return undefined for nonexistent profile name', async () => { const profile = resolver.getProfileByName('NONEXISTENT'); expect(profile).toBeUndefined(); }); tap.test('should return undefined for nonexistent target name', async () => { const target = resolver.getTargetByName('NONEXISTENT'); expect(target).toBeUndefined(); }); export default tap.start();