import 'package:flutter_test/flutter_test.dart'; import 'package:halkyon_app/halkyon_app.dart'; import 'package:halkyon_app/testing.dart'; void main() { final builtIn = Instance.parse('learn.halkyon.eu')!; final other = Instance.parse('learn.example.org')!; late MemoryInstanceStore store; late List forgotten; InstanceChoice choice() => InstanceChoice( store, builtIn: builtIn, forget: (instance) async { forgotten.add(instance); }, sharesStorage: InstanceStorage('/support', builtIn: builtIn).sharesWith, ); setUp(() { store = MemoryInstanceStore(); forgotten = []; }); test('a new install has no instance until one is chosen', () async { final instances = choice(); await instances.load(usedBefore: false); expect(instances.current, isNull); expect(instances.known, isEmpty); await instances.use(other); expect(instances.current, other); expect(store.kept?.current, other); expect(store.kept?.known, [other]); }); test('an install from before there was a choice goes on with the ' "build's instance", () async { final instances = choice(); await instances.load(usedBefore: true); expect(instances.current, builtIn); expect(store.kept?.current, builtIn); expect(store.kept?.known, [builtIn]); }); test('what was chosen is read back', () async { store = MemoryInstanceStore((current: other, known: [builtIn, other])); final instances = choice(); await instances.load(usedBefore: true); expect(instances.current, other); expect(instances.known, [builtIn, other]); }); test('switching keeps every account; removing one forgets what the ' 'device holds of it, never of the one in use', () async { final instances = choice(); await instances.load(usedBefore: true); await instances.use(other); await instances.use(builtIn); expect(instances.known, [builtIn, other]); await instances.remove(builtIn); expect(forgotten, isEmpty, reason: 'in use'); await instances.remove(other); expect(forgotten, [other]); expect(instances.known, [builtIn]); expect(store.kept?.current, builtIn); expect(store.kept?.known, [builtIn]); }); test('tells its listeners of every change', () async { final instances = choice(); var changes = 0; instances.addListener(() => changes++); await instances.load(usedBefore: false); await instances.use(other); await instances.use(builtIn); await instances.remove(other); expect(changes, 4); }); test('allows an instance reached securely, and over plain HTTP elsewhere ' "only the build's own in a development build", () { final insecure = Instance.parse('http://10.0.2.2:8080')!; // A development build, as tests run. final development = InstanceChoice( store, builtIn: insecure, forget: (instance) async {}, sharesStorage: (a, b) => false, ); expect(development.allows(builtIn), isTrue); expect( development.allows(Instance.parse('http://localhost:8080')!), isTrue, ); expect(development.allows(insecure), isTrue); expect( development.allows(Instance.parse('http://learn.example.org')!), isFalse, ); final release = InstanceChoice( store, builtIn: insecure, forget: (instance) async {}, allowsInsecureBuiltIn: false, sharesStorage: (a, b) => false, ); expect(release.allows(insecure), isFalse); }); test('leaves an instance it no longer allows for another, forgetting it ' 'if asked', () async { final insecure = Instance.parse('http://learn.example.net')!; store = MemoryInstanceStore(( current: insecure, known: [other, insecure, builtIn], )); final instances = choice(); await instances.load(usedBefore: false); expect(instances.current, insecure, reason: 'never dropped silently'); await instances.leave(insecure, forget: false); expect(instances.current, other, reason: 'the first allowed one'); expect(instances.known, [other, insecure, builtIn]); expect(forgotten, isEmpty); await instances.use(insecure); await instances.leave(insecure, forget: true); expect(instances.current, other); expect(instances.known, [other, builtIn]); expect(forgotten, [insecure]); expect(store.kept?.known, [other, builtIn]); }); test("leaving the only one goes to the build's own", () async { final insecure = Instance.parse('http://learn.example.net')!; store = MemoryInstanceStore((current: insecure, known: [insecure])); final instances = choice(); await instances.load(usedBefore: false); await instances.leave(insecure, forget: true); expect(instances.current, builtIn); expect(instances.known, [builtIn]); expect(forgotten, [insecure]); }); group('two addresses the device keeps in one place (http:// and https:// ' 'on one host; the reading of the fixes)', () { final insecure = Instance.parse('http://learn.example.org')!; test('are one account: choosing one puts it in the place of the other, ' 'forgetting nothing', () async { store = MemoryInstanceStore(( current: insecure, known: [builtIn, insecure], )); final instances = choice(); await instances.load(usedBefore: false); await instances.use(other); expect(instances.current, other); expect(instances.known, [builtIn, other]); expect(forgotten, isEmpty); }); test('removing one never forgets the folder, session or keys the other ' 'uses', () async { // Both known, from before. store = MemoryInstanceStore(( current: insecure, known: [builtIn, insecure, other], )); final instances = choice(); await instances.load(usedBefore: false); // Refused, and left for good, as the screen for an address over // plain HTTP offers. await instances.leave(insecure, forget: true); expect(instances.known, [builtIn, other]); expect(forgotten, isEmpty); store = MemoryInstanceStore(( current: other, known: [builtIn, insecure, other], )); final again = choice(); await again.load(usedBefore: false); await again.remove(insecure); expect(again.known, [builtIn, other]); expect(forgotten, isEmpty); }); }); test('the storage tells which addresses it keeps in one place, the ' "build's own apart", () { final storage = InstanceStorage('/support', builtIn: builtIn); final insecure = Instance.parse('http://learn.example.org')!; final builtInsTwin = Instance.parse('http://learn.halkyon.eu')!; expect(storage.sharesWith(insecure, other), isTrue); expect(storage.sharesWith(other, other), isFalse); expect(storage.sharesWith(builtIn, builtInsTwin), isFalse); expect( storage.sharesWith(other, Instance.parse('learn.example.net')!), isFalse, ); // Their places as before: none moved. expect( storage.peopleOf(other).path, '/support/instances/learn.example.org/people', ); expect(storage.peopleOf(builtIn).path, '/support/people'); }); test("an account's wait is kept per instance, as its keys are (Halkyon " "Learn's ADR 0021, D31)", () { final storage = InstanceStorage('/support', builtIn: builtIn); expect(storage.waitOf(builtIn), 'halkyon_account_wait'); expect(storage.waitOf(other), 'halkyon_account_wait@learn.example.org'); expect(storage.waitOf(other), isNot(storage.keysOf(other))); }); }