halkyon/halkyon_learn_flutter/test/support/fake_feedback_gateways.dart

116 lines
3.2 KiB
Dart
Raw Normal View History

feat(feedback): reports and suggestions on published studysets, gathered by the instance Stage 4 of the public tier (ADR 0005, rule 6), its server and the app's data. A learner writes a report or a suggestion on a published studyset, a card, note or step of it, in their own synced space, so it works offline; replies and the author's decisions (closed, hidden, accepted, declined) likewise, in the writer's or the author's space. The instance gathers them: public to everyone signed in, but for what the author hides; for the author's inbox and the writer's. What counts is decided as rows are read, since a modified app could write anything: threads from people with an identity and a public profile the author hasn't blocked, replies from the writer and the author alone, decisions from the author alone, no suggestion under "no derivatives". The author gets the files a suggestion names from its writer's, and the next release credits the writers of the suggestions accepted since the last (Co-authored-by). Deleting a person deletes their blocks and what they read; what they wrote goes with their space. In the app, the repository writes reports and suggestions (a card, note or step as the learner has it, without what is theirs alone, with the files it names) and takes an accepted suggestion into the author's own card, note or step. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MMm7usAo6q9ZZRoPkWjoxi Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-10 01:04:17 +02:00
import 'dart:convert';
import 'dart:typed_data';
import 'package:halkyon_learn_flutter/src/feature/feedback/data/feedback_gateway.dart';
import 'package:halkyon_learn_flutter/src/feature/feedback/model/feedback.dart';
/// A [FeedbackWriteGateway] in memory: what was written, in order.
class FakeFeedbackWriteGateway() implements FeedbackWriteGateway {
/// The threads written: their published studyset, kind and payload JSON.
final threads =
<
({String publicationId, FeedbackKind kind, Map<String, Object?> json})
>[];
/// The replies written, by thread.
final messages = <({String threadId, Map<String, Object?> json})>[];
/// The decisions written, by thread.
final decisions = <({String threadId, FeedbackDecision decision})>[];
@override
Future<String> insertThread({
required String publicationId,
required FeedbackKind kind,
required ByteData payload,
}) async {
threads.add((
publicationId: publicationId,
kind: kind,
json: _json(payload),
));
return 'thread-${threads.length}';
}
@override
Future<void> insertMessage(String threadId, ByteData payload) async =>
messages.add((threadId: threadId, json: _json(payload)));
@override
Future<void> insertDecision(
String threadId,
FeedbackDecision decision,
) async => decisions.add((threadId: threadId, decision: decision));
static Map<String, Object?> _json(ByteData payload) =>
jsonDecode(utf8.decode(Uint8List.sublistView(payload)))
as Map<String, Object?>;
}
/// A [FeedbackServerGateway] in memory: [threads] as the instance gathers
/// them (all public, for the inbox too), [files] by SHA-256.
class FakeFeedbackServerGateway({
final List<FeedbackThread> threads = const [],
final Map<String, Uint8List> files = const {},
}) implements FeedbackServerGateway {
late var _threads = [...threads];
/// The threads read, and the people blocked.
final seen = <String>[];
final blocked = <String>[];
/// When set, every request fails with it.
Exception? fail;
/// Replaces what the instance gathers.
set gathered(List<FeedbackThread> threads) => _threads = [...threads];
Future<void> _maybeFail() async {
if (fail case final error?) {
throw error;
}
}
@override
Future<List<FeedbackThread>> threadsOf(String publicId) async {
await _maybeFail();
return [
for (final thread in _threads)
if (thread.publicId == publicId) thread,
];
}
@override
Future<List<FeedbackThread>> inbox() async {
await _maybeFail();
return [
for (final thread in _threads)
if (thread.mine || thread.forMe) thread,
];
}
@override
Future<int> unseen() async =>
(await inbox()).where((thread) => thread.unseen).length;
@override
Future<void> markSeen(String threadId) async {
await _maybeFail();
seen.add(threadId);
}
@override
Future<void> block(String handle) async => blocked.add(handle);
@override
Future<void> unblock(String handle) async => blocked.remove(handle);
@override
Future<List<String>> blockedPeople() async => [...blocked];
@override
Future<Uint8List> file(String threadId, String sha256) async {
await _maybeFail();
return files[sha256]!;
}
}