feat: add Gemini and Spartan protocol support
This commit is contained in:
parent
a4a49f41d7
commit
fa7aa5557c
48 changed files with 4992 additions and 161 deletions
103
app/test/browser_controller_test.dart
Normal file
103
app/test/browser_controller_test.dart
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
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<LoadFailed>());
|
||||
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<CertificateChanged>());
|
||||
|
||||
// Trusting records the presented certificate as the host's new pin.
|
||||
await controller.trustPresentedCertificate();
|
||||
expect(await db.allPins(), {'capsule.example:1965': presented});
|
||||
});
|
||||
|
||||
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<ClientCertificateRequired>());
|
||||
|
||||
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');
|
||||
});
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue