halkyon/halkyon_learn_flutter/test/support/fake_assistant_gateway.dart
Tizian 「ティツィアーン」 9f1716f16d
fix(assistant): single use under racing requests, a cap on registering, Caddy reloaded
A connect code, an authorization code and a refresh token are each
consumed by deleting their row and going on only if that deleted it, so
of two requests racing with the same one only one succeeds (as OAuth
requires); tests race them. Registration, open to anyone, takes at most
200 clients an hour (429 after), so a flood can't fill the table before
pruning. The deploy reloads Caddy in place when deploy/Caddyfile changed,
as `docker compose up` doesn't restart it for a file it mounts: without
it the connector's paths wouldn't reach the web server.

Also formats the app's fake assistant gateway, new and so unchecked by
the last commit's check (tool/check.sh formats tracked files only).

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 15:11:18 +02:00

53 lines
1.5 KiB
Dart

import 'package:halkyon_learn_flutter/src/feature/assistant/data/assistant_gateway.dart';
import 'package:halkyon_learn_flutter/src/feature/assistant/model/connected_assistant.dart';
/// An instance offering the connector at [url] (none when null), with
/// [initially] connected; [unreachable] makes every call fail as offline.
class FakeAssistantGateway({
/// Where the connector is; none when null.
final String? url = 'https://learn.example.org/mcp',
/// The assistants connected at first.
final List<ConnectedAssistant> initially = const [],
}) implements AssistantGateway {
/// Whether the instance can't be reached, failing every call while set.
bool unreachable = false;
/// The assistants connected.
late final List<ConnectedAssistant> connected = [...initially];
/// The codes given out, the last one valid.
final codes = <String>[];
void _reach() {
if (unreachable) {
throw Exception('offline');
}
}
@override
Future<String?> connectorUrl() async {
_reach();
return url;
}
@override
Future<ConnectCode> connectCode() async {
_reach();
final code = 'K7QX-M2P${codes.length}';
codes.add(code);
return (code: code, expiresAt: DateTime.utc(2026, 10, 10, 12, 10));
}
@override
Future<List<ConnectedAssistant>> connections() async {
_reach();
return [...connected];
}
@override
Future<void> disconnect(int id) async {
_reach();
connected.removeWhere((assistant) => assistant.id == id);
}
}