/* * Copyright (c) 2024-2026 Fabian Freund. * * This file is part of WebLibre * (see https://weblibre.eu). * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ import 'package:flutter_test/flutter_test.dart'; import 'package:weblibre/features/app_links/domain/services/effective_routing.dart'; import 'package:weblibre/features/geckoview/features/tabs/data/models/site_assignment.dart'; import 'package:weblibre/features/proxy/data/proxy_connection.dart'; SiteAssignment _assignment(String site, {String? contextId}) => SiteAssignment(id: site, contextualIdentity: contextId, assignedSite: site); void main() { group('resolveContainerAssignment', () { test('explicit proxy connection wins', () { final assignment = resolveContainerAssignment( contextId: 'ctx', proxyConnectionId: const TorProxyConnectionId(), bypassGlobalProxy: false, ); expect(assignment, isA()); }); test('bypassGlobalProxy with no proxy is direct scoped to the context', () { final assignment = resolveContainerAssignment( contextId: 'ctx', proxyConnectionId: null, bypassGlobalProxy: true, ); expect(assignment, isA()); expect((assignment as DirectProxyAssignment).scopeId, 'ctx'); }); test('no proxy and no bypass inherits', () { final assignment = resolveContainerAssignment( contextId: 'ctx', proxyConnectionId: null, bypassGlobalProxy: false, ); expect(assignment, isA()); }); }); group('resolveIsolationContextRouting', () { test('any explicit proxy wins (lowest sorted id)', () { final routing = resolveIsolationContextRouting([ ProxyAssignment.inherit(), ProxyAssignment.explicit('zeta'), ProxyAssignment.explicit('alpha'), ProxyAssignment.direct('scope'), ]); expect(routing.chosen, isA()); expect((routing.chosen as ExplicitProxyAssignment).proxyId, 'alpha'); expect(routing.distinctAssignmentCount, 4); }); test('direct wins only when no container inherits', () { final routing = resolveIsolationContextRouting([ ProxyAssignment.direct('scopeB'), ProxyAssignment.direct('scopeA'), ]); expect(routing.chosen, isA()); expect((routing.chosen as DirectProxyAssignment).scopeId, 'scopeA'); }); test('direct plus inherit collapses to inherit', () { final routing = resolveIsolationContextRouting([ ProxyAssignment.direct('scope'), ProxyAssignment.inherit(), ]); expect(routing.chosen, isA()); expect(routing.distinctAssignmentCount, 2); expect(routing.assignmentLabels, ['inherit', 'direct:scope']); }); }); group('isAssignmentProtected', () { test('explicit is always protected', () { expect( isAssignmentProtected( ProxyAssignment.explicit('p'), protectGeneralContext: false, ), isTrue, ); }); test('direct is never protected', () { expect( isAssignmentProtected( ProxyAssignment.direct('s'), protectGeneralContext: true, ), isFalse, ); }); test('inherit follows the general context', () { expect( isAssignmentProtected( ProxyAssignment.inherit(), protectGeneralContext: true, ), isTrue, ); expect( isAssignmentProtected( ProxyAssignment.inherit(), protectGeneralContext: false, ), isFalse, ); }); }); group('protectedTargetPatternForSite', () { test('wildcard entry includes subdomains and ignores port', () { final pattern = protectedTargetPatternForSite( Uri.parse('https://*.example.com'), ); expect(pattern.scheme, 'https'); expect(pattern.hostOrSuffix, 'example.com'); expect(pattern.includeSubdomains, isTrue); expect(pattern.port, isNull); }); test('exact entry preserves effective port', () { final defaultPort = protectedTargetPatternForSite( Uri.parse('https://example.com'), ); expect(defaultPort.hostOrSuffix, 'example.com'); expect(defaultPort.includeSubdomains, isFalse); expect(defaultPort.port, 443); final explicitPort = protectedTargetPatternForSite( Uri.parse('http://example.com:8080'), ); expect(explicitPort.port, 8080); }); }); group('computeProtectedTargetPatterns', () { test('keeps only assignments in a protected or strict container', () { final patterns = computeProtectedTargetPatterns( assignments: [ _assignment('https://proxied.example', contextId: 'proxied'), _assignment('https://direct.example', contextId: 'direct'), _assignment('https://strict.example', contextId: 'strict'), _assignment('https://unassigned.example', contextId: null), ], protectedOrStrictContextIds: {'proxied', 'strict'}, ); final hosts = patterns.map((p) => p.hostOrSuffix).toSet(); expect(hosts, {'proxied.example', 'strict.example'}); }); test('deduplicates identical patterns', () { final patterns = computeProtectedTargetPatterns( assignments: [ _assignment('https://dup.example', contextId: 'a'), _assignment('https://dup.example', contextId: 'a'), ], protectedOrStrictContextIds: {'a'}, ); expect(patterns.length, 1); }); }); }