import 'package:gametime_server/api/router.dart'; import 'package:gametime_server/api/share_api.dart'; import 'package:gametime_server/application/application.dart'; import 'package:gametime_server/domain/domain.dart'; import 'package:shelf/shelf.dart'; import 'package:test/test.dart'; void main() { test('share routes require bearer authentication', () async { final handler = buildApiHandler(shareApi: _shareApi()); final requests = [ Request('POST', Uri.parse('http://localhost/shares')), Request('GET', Uri.parse('http://localhost/shares/inbox')), Request('POST', Uri.parse('http://localhost/shares/share-1/accept')), Request('POST', Uri.parse('http://localhost/shares/share-1/decline')), Request('POST', Uri.parse('http://localhost/shares/share-1/revoke')), ]; for (final request in requests) { final response = await handler(request); expect(response.statusCode, 401, reason: request.url.path); } }); } ShareApi _shareApi() { final clock = _FakeClock(DateTime.utc(2026, 7, 19, 12)); final ids = _FakeIds(); final users = _FakeUserRepository( UserAccount( id: 'user-1', email: 'user@example.com', passwordHash: 'hash', createdAt: clock.now(), updatedAt: clock.now(), ), ); final sessions = _FakeAuthSessionRepository( AuthSession( id: 'session-1', userId: 'user-1', tokenHash: 'token-hash:valid-token', issuedAt: clock.now(), expiresAt: clock.now().add(const Duration(days: 1)), ), ); final tokens = _FakeTokenService(); final authenticate = AuthenticateRequestUseCase( users: users, sessions: sessions, tokens: tokens, clock: clock, ); final shares = _FakeShareRepository(); final resources = _FakeSyncedResourceRepository(); return ShareApi( createShare: CreateShareUseCase( users: users, shares: shares, clock: clock, ids: ids, ), listInbox: ListInboxUseCase(shares: shares), acceptShare: AcceptShareUseCase( shares: shares, resources: resources, clock: clock, ids: ids, ), declineShare: DeclineShareUseCase(shares: shares, clock: clock), revokeShare: RevokeShareUseCase(shares: shares, clock: clock), authenticateRequest: authenticate, ); } final class _FakeUserRepository implements UserRepository { const _FakeUserRepository(this.user); final UserAccount user; @override Future findByEmail(String email) async { return user.email == email.toLowerCase() ? user : null; } @override Future findById(String id) async { return id == user.id ? user : null; } @override Future insert(UserAccount user) async {} @override Future updatePasswordHash({ required String userId, required String passwordHash, required DateTime updatedAt, }) async {} } final class _FakeAuthSessionRepository implements AuthSessionRepository { const _FakeAuthSessionRepository(this.session); final AuthSession session; @override Future findByTokenHash(String tokenHash) async { return tokenHash == session.tokenHash ? session : null; } @override Future insert(AuthSession session) async {} @override Future revoke({ required String sessionId, required DateTime revokedAt, }) async {} } final class _FakeShareRepository implements ShareRepository { @override Future insertShare({ required Share share, required List recipients, }) async {} @override Future> listInbox(String recipientUserId) async { return const []; } @override Future findShareById(String shareId) async { return null; } @override Future findRecipient({ required String shareId, required String recipientUserId, }) async { return null; } @override Future updateRecipientStatus({ required String recipientId, required ShareRecipientStatus status, required DateTime respondedAt, }) async {} @override Future revokeShare({ required String shareId, required DateTime revokedAt, }) async {} } final class _FakeSyncedResourceRepository implements SyncedResourceRepository { @override Future upsertWithLww(SyncedResource resource) async { return SyncWriteResult( status: SyncWriteStatus.accepted, resource: resource, ); } @override Future> findAllForUserSince({ required String ownerUserId, DateTime? since, }) async { return const []; } } final class _FakeTokenService implements OpaqueTokenService { @override String generateToken() => 'valid-token'; @override String hashToken(String token) => 'token-hash:$token'; } final class _FakeClock implements Clock { const _FakeClock(this.value); final DateTime value; @override DateTime now() => value; } final class _FakeIds implements IdGenerator { var _next = 0; @override String newId() { _next += 1; return 'id-$_next'; } }