import 'dart:io'; import 'package:drift/native.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mews/application/controllers/browser_controller.dart'; import 'package:mews/application/key_store.dart'; import 'package:mews/database/database.dart'; import 'package:mews/protocols/tofu.dart'; import 'fake_fetch.dart'; void main() { late MewsDatabase db; late Directory keyDir; late KeyStore keys; setUp(() async { db = MewsDatabase.test(NativeDatabase.memory()); keyDir = await Directory.systemTemp.createTemp('mews-keys'); keys = KeyStore(() async => keyDir); }); tearDown(() async { await db.close(); keyDir.deleteSync(recursive: true); }); BrowserController browser(FakeFetch fetch) => BrowserController(db: db, fetch: fetch, keys: keys); test('a bare hostname that answers on gemini is found there', () async { final controller = browser(FakeFetch({'gemini://capsule.example': '# Hello\n\nText.'})); await controller.open('capsule.example'); expect(controller.error.value, isNull); expect(controller.currentPage.value!.url, 'gemini://capsule.example'); expect(controller.currentPage.value!.title, 'Hello'); }); test('a bare hostname that answers on spartan is found there', () async { final controller = browser(FakeFetch({'spartan://capsule.example': '# Hello\n\nText.'})); await controller.open('capsule.example'); expect(controller.error.value, isNull); expect(controller.currentPage.value!.url, 'spartan://capsule.example'); }); test('an explicit scheme is not second-guessed', () async { final controller = browser(FakeFetch({'gemini://capsule.example': '# Hello\n\nText.'})); // The reader named the web, so a missing web page is an error, not an // invitation to try the capsule protocols. await controller.open('https://capsule.example'); expect(controller.error.value, isA()); expect(controller.currentPage.value, isNull); }); test('a changed certificate blocks until the reader trusts the new one', () async { final presented = TofuPin(fingerprint: 'new', expires: DateTime(2036)); final controller = browser(FakeFetch( {'gemini://capsule.example': '# Hello\n\nText.'}, changedCertificates: {'gemini://capsule.example': presented}, )); await controller.open('gemini://capsule.example'); expect(controller.error.value, isA()); // Trusting records the presented certificate as the host's new pin. await controller.trustPresentedCertificate(); expect(await db.allPins(), {'capsule.example:1965': presented}); }); test('a failed load keeps the address the reader followed', () async { final controller = browser(FakeFetch(const {})); // Neither the address nor its wap. subdomain answers; the wap. guess // is not left standing in for what the reader followed. await controller.open('https://capsule.example/post'); expect(controller.error.value, isA()); expect(controller.currentAddress, 'https://capsule.example/post'); }); test('a certificate request is answered by binding an identity', () async { final controller = browser(FakeFetch( {'gemini://capsule.example': '# Hello\n\nText.'}, certificateRequired: {'gemini://capsule.example'}, )); final id = await db.addClientCertificate('Reader', 'CERT PEM'); await keys.save(id, 'KEY PEM'); // The site asks before anything shows; the reader's answer is a choice, // not an automatic presentation. await controller.open('gemini://capsule.example'); expect(controller.error.value, isA()); await controller.chooseCertificate(id); expect(await db.bindingFor('capsule.example', 1965), id); expect(controller.error.value, isNull); expect(controller.currentPage.value!.title, 'Hello'); // The binding is worn on the next visit too, without asking again. await controller.open('gemini://capsule.example'); expect(controller.error.value, isNull); expect(controller.currentPage.value!.title, 'Hello'); }); }