import 'package:commons_core/commons_core.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:tane/state/peer_rating_cubit.dart'; /// In-memory [RatingTransport]: rate/retract act as this device's identity, /// one live rating per (rater, subject) like the Nostr transport. class FakeRatingTransport implements RatingTransport { FakeRatingTransport(this.selfId); final String selfId; final Map> _bySubject = {}; // subject -> rater /// Test helper: record an arbitrary rater→subject rating. void addRating(String rater, String subject, int stars, {String comment = ''}) => (_bySubject[subject] ??= {})[rater] = Rating( rater: rater, subject: subject, stars: stars, ratedAt: DateTime(2026), comment: comment, ); @override Future rate({ required String subjectPubkey, required int stars, String comment = '', }) async => addRating(selfId, subjectPubkey, stars, comment: comment); @override Future retract({required String subjectPubkey}) async => _bySubject[subjectPubkey]?.remove(selfId); @override Future> ratingsOf(String subjectPubkey) async => List.of(_bySubject[subjectPubkey]?.values ?? const []); @override Future myRatingOf(String subjectPubkey) async => _bySubject[subjectPubkey]?[selfId]; @override Future close() async {} } /// Minimal trust transport exposing a fixed certification graph, for the /// circle weighting. class FakeTrustTransport implements TrustTransport { final List certs = []; void addCert(String issuer, String subject) => certs.add(Certification( issuer: issuer, subject: subject, issuedAt: DateTime(2026), )); @override Future> allCertifications() async => List.of(certs); @override Future> certifiersOf(String subjectPubkey) async => { for (final c in certs) if (c.subject == subjectPubkey) c.issuer, }; @override Future certify({ required String subjectPubkey, Duration validity = const Duration(days: 365), String note = '', }) async {} @override Future revoke({required String subjectPubkey}) async {} @override Future close() async {} } void main() { const me = 'me'; const peer = 'peer'; test('aggregates count and average over active ratings', () async { final ratings = FakeRatingTransport(me) ..addRating('a', peer, 5) ..addRating('b', peer, 2); final cubit = PeerRatingCubit(ratings, peerPubkey: peer, selfPubkey: me); await cubit.load(); expect(cubit.state.count, 2); expect(cubit.state.average, closeTo(3.5, 0.001)); expect(cubit.state.circleCount, 0); // no trust graph wired expect(cubit.state.iRated, isFalse); await cubit.close(); }); test('counts ratings coming from your circle', () async { final ratings = FakeRatingTransport(me) ..addRating('friend', peer, 5) // you vouch for 'friend' ..addRating('stranger', peer, 1); final trust = FakeTrustTransport()..addCert(me, 'friend'); final cubit = PeerRatingCubit( ratings, peerPubkey: peer, selfPubkey: me, trust: trust, ); await cubit.load(); expect(cubit.state.count, 2); expect(cubit.state.circleCount, 1); await cubit.close(); }); test('rate publishes then reloads; re-rating replaces', () async { final ratings = FakeRatingTransport(me); final cubit = PeerRatingCubit(ratings, peerPubkey: peer, selfPubkey: me); await cubit.load(); await cubit.rate(4, comment: 'Nice seeds'); expect(cubit.state.myStars, 4); expect(cubit.state.myComment, 'Nice seeds'); expect(cubit.state.count, 1); await cubit.rate(2); expect(cubit.state.myStars, 2); expect(cubit.state.count, 1); // replaced, not piled up await cubit.close(); }); test('retract removes your rating', () async { final ratings = FakeRatingTransport(me)..addRating(me, peer, 3); final cubit = PeerRatingCubit(ratings, peerPubkey: peer, selfPubkey: me); await cubit.load(); expect(cubit.state.iRated, isTrue); await cubit.retract(); expect(cubit.state.iRated, isFalse); expect(cubit.state.count, 0); await cubit.close(); }); test('never rates self', () async { final ratings = FakeRatingTransport(me); final cubit = PeerRatingCubit(ratings, peerPubkey: me, selfPubkey: me); await cubit.load(); await cubit.rate(5); expect(cubit.state.iRated, isFalse); expect(cubit.state.count, 0); await cubit.close(); }); test('offline (no transport) never throws', () async { final cubit = PeerRatingCubit(null, peerPubkey: peer, selfPubkey: me); expect(cubit.isOnline, isFalse); await cubit.load(); await cubit.rate(5); await cubit.retract(); expect(cubit.state.loading, isFalse); await cubit.close(); }); }