A network-wide 'N people vouch' count invites spam and means little. Now the
trust banner computes whether the peer is within YOUR circle — you vouch for
them, or someone you vouch for does (friend-of-a-friend) — via the pure
WebOfTrust rule (seeds={you}, threshold 1, distance 2). When known, the banner
turns green with 'In your circle' (else it falls back to the count).
The full member policy (threshold/distance/bootstrap) stays open (network-trust
§2); this is a deliberately loose 'people near you' heuristic.
Analyzer clean; trust cubit test adds a friend-of-a-friend vs stranger case
(run flutter test locally to confirm — the widget suite is too slow to run here).
111 lines
3.5 KiB
Dart
111 lines
3.5 KiB
Dart
import 'package:commons_core/commons_core.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:tane/state/trust_cubit.dart';
|
|
|
|
/// In-memory [TrustTransport]: certify/revoke act as this device's identity.
|
|
class FakeTrustTransport implements TrustTransport {
|
|
FakeTrustTransport(this.selfId);
|
|
final String selfId;
|
|
final Map<String, Set<String>> _certs = {}; // subject -> issuers
|
|
|
|
/// Test helper: record an arbitrary issuer→subject vouch (to build a graph).
|
|
void addCert(String issuer, String subject) =>
|
|
(_certs[subject] ??= {}).add(issuer);
|
|
|
|
@override
|
|
Future<Set<String>> certifiersOf(String subjectPubkey) async =>
|
|
_certs[subjectPubkey] ?? <String>{};
|
|
|
|
@override
|
|
Future<void> certify({
|
|
required String subjectPubkey,
|
|
Duration validity = const Duration(days: 365),
|
|
String note = '',
|
|
}) async =>
|
|
addCert(selfId, subjectPubkey);
|
|
|
|
@override
|
|
Future<void> revoke({required String subjectPubkey}) async =>
|
|
_certs[subjectPubkey]?.remove(selfId);
|
|
|
|
@override
|
|
Future<List<Certification>> allCertifications() async => [
|
|
for (final entry in _certs.entries)
|
|
for (final issuer in entry.value)
|
|
Certification(
|
|
issuer: issuer,
|
|
subject: entry.key,
|
|
issuedAt: DateTime(2026),
|
|
),
|
|
];
|
|
|
|
@override
|
|
Future<void> close() async {}
|
|
}
|
|
|
|
void main() {
|
|
const me = 'me';
|
|
const peer = 'peer';
|
|
|
|
test('loads certifier count and whether I vouch', () async {
|
|
final transport = FakeTrustTransport(me);
|
|
await transport.certify(subjectPubkey: peer); // I already vouch
|
|
final cubit = TrustCubit(transport, peerPubkey: peer, selfPubkey: me);
|
|
await cubit.load();
|
|
expect(cubit.state.certifierCount, 1);
|
|
expect(cubit.state.iVouch, isTrue);
|
|
expect(cubit.state.loading, isFalse);
|
|
await cubit.close();
|
|
});
|
|
|
|
test('toggleVouch certifies then revokes', () async {
|
|
final cubit =
|
|
TrustCubit(FakeTrustTransport(me), peerPubkey: peer, selfPubkey: me);
|
|
await cubit.load();
|
|
expect(cubit.state.iVouch, isFalse);
|
|
|
|
await cubit.toggleVouch();
|
|
expect(cubit.state.iVouch, isTrue);
|
|
expect(cubit.state.certifierCount, 1);
|
|
|
|
await cubit.toggleVouch();
|
|
expect(cubit.state.iVouch, isFalse);
|
|
expect(cubit.state.certifierCount, 0);
|
|
await cubit.close();
|
|
});
|
|
|
|
test('a friend-of-a-friend is in your circle; a stranger is not', () async {
|
|
final transport = FakeTrustTransport(me)
|
|
..addCert(me, 'friend') // you vouch for a friend
|
|
..addCert('friend', peer) // your friend vouches for the peer
|
|
..addCert('stranger', 'other'); // unrelated to you
|
|
|
|
final inCircle = TrustCubit(transport, peerPubkey: peer, selfPubkey: me);
|
|
await inCircle.load();
|
|
expect(inCircle.state.knownToYou, isTrue);
|
|
await inCircle.close();
|
|
|
|
final outside = TrustCubit(transport, peerPubkey: 'other', selfPubkey: me);
|
|
await outside.load();
|
|
expect(outside.state.knownToYou, isFalse);
|
|
await outside.close();
|
|
});
|
|
|
|
test('never vouches for self', () async {
|
|
final cubit =
|
|
TrustCubit(FakeTrustTransport(me), peerPubkey: me, selfPubkey: me);
|
|
await cubit.load();
|
|
await cubit.toggleVouch();
|
|
expect(cubit.state.iVouch, isFalse);
|
|
await cubit.close();
|
|
});
|
|
|
|
test('offline (no transport) never throws', () async {
|
|
final cubit = TrustCubit(null, peerPubkey: peer, selfPubkey: me);
|
|
expect(cubit.isOnline, isFalse);
|
|
await cubit.load();
|
|
await cubit.toggleVouch();
|
|
expect(cubit.state.loading, isFalse);
|
|
await cubit.close();
|
|
});
|
|
}
|