tane/apps/app_seeds/test/state/trust_cubit_test.dart
vjrj 163d659119 feat(block2): 'in your circle' trust — friend-of-a-friend, not a raw count
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).
2026-07-10 12:10:06 +02:00

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();
});
}