MotorSignal-App/test/account_cloud_cleanup_servi...

205 lines
5.9 KiB
Dart

import 'dart:async';
import 'package:http/http.dart' as http;
import 'package:http/testing.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:motorcycle_dash/services/auth/auth_service.dart';
import 'package:motorcycle_dash/services/auth/auth_state.dart';
import 'package:motorcycle_dash/services/auth/auth_user.dart';
import 'package:motorcycle_dash/services/cloud/account_cloud_cleanup_service.dart';
class _Auth implements AuthService {
@override Future<void> ensureBackendIdentity() async {}
final _states = StreamController<AuthState>.broadcast();
String? token = 'test-token';
AuthUser? user = const AuthUser(
uid: 'cleanup-test',
email: 'cleanup@test.com',
providerId: 'password',
emailVerified: true,
);
@override
Stream<AuthState> get authStateChanges => _states.stream;
@override
AuthState get currentState => user == null
? const AuthState.unauthenticated(isFirebaseConfigured: true)
: AuthState.authenticated(user!, isFirebaseConfigured: true);
@override
AuthUser? get currentUser => user;
@override
bool get isConfigured => true;
@override
bool get isEmailVerified => true;
@override
Future<void> reauthenticate({String? password}) async {}
@override
Future<void> deleteCurrentUser() async {}
@override
Future<String?> getIdToken([bool forceRefresh = false]) async => token;
@override
Future<void> signInWithEmailAndPassword(String email, String password) async {}
@override
Future<void> createUserWithEmailAndPassword(String email, String password, {String? displayName}) async {}
@override
Future<void> signInWithGoogle() async {}
@override
Future<void> signInAnonymously() async {}
@override
Future<void> sendPasswordResetEmail(String email) async {}
@override
Future<void> sendEmailVerification() async {}
@override
Future<void> reloadCurrentUser() async {}
@override
Future<void> signOut() async {}
@override
void dispose() => _states.close();
}
AccountCloudCleanupService _service(_Auth auth, int status, String body) {
return AccountCloudCleanupService(
auth: auth,
client: MockClient((request) async {
expect(request.method, 'DELETE');
expect(request.url.path, '/account/cloud-data');
expect(request.headers['authorization'], 'Bearer test-token');
return http.Response(body, status);
}),
workerUrl: 'https://cleanup.test',
);
}
void main() {
test('accepts a valid Worker 200 response before Firestore cleanup', () async {
final auth = _Auth();
addTearDown(auth.dispose);
await expectLater(
_service(auth, 200, '{"success":true,"deletedObjects":0}').deleteCloudData(),
throwsA(isA<StateError>().having(
(error) => error.message,
'message',
'MotorCloud database is unavailable.',
)),
);
});
test('accepts an idempotent empty-prefix 204 response', () async {
final auth = _Auth();
addTearDown(auth.dispose);
await expectLater(
_service(auth, 204, '').deleteCloudData(),
throwsA(isA<StateError>().having(
(error) => error.message,
'message',
'MotorCloud database is unavailable.',
)),
);
});
test('rejects a malformed Worker success response', () async {
final auth = _Auth();
addTearDown(auth.dispose);
await expectLater(
_service(auth, 200, '{"deleted":0}').deleteCloudData(),
throwsA(isA<StateError>().having(
(error) => error.message,
'message',
'MotorCloud cleanup returned an invalid response.',
)),
);
});
test('rejects an empty Worker success response', () async {
final auth = _Auth();
addTearDown(auth.dispose);
await expectLater(
_service(auth, 200, '').deleteCloudData(),
throwsA(isA<StateError>().having(
(error) => error.message,
'message',
'MotorCloud cleanup returned an invalid response.',
)),
);
});
for (final status in [401, 403, 500]) {
test('surfaces Worker HTTP $status as controlled cleanup failure', () async {
final auth = _Auth();
addTearDown(auth.dispose);
await expectLater(
_service(auth, status, '{"error":"failure"}').deleteCloudData(),
throwsA(isA<StateError>().having(
(error) => error.message,
'message',
'MotorCloud cleanup was not completed ($status).',
)),
);
});
}
test('rejects an empty token before making a Worker request', () async {
final auth = _Auth()..token = null;
addTearDown(auth.dispose);
await expectLater(
_service(auth, 200, '').deleteCloudData(),
throwsA(isA<StateError>().having(
(error) => error.message,
'message',
'A valid account session is required.',
)),
);
});
test('rejects an anonymous account before making a Worker request', () async {
final auth = _Auth()
..user = const AuthUser(uid: 'guest', isAnonymous: true);
addTearDown(auth.dispose);
var requests = 0;
final service = AccountCloudCleanupService(
auth: auth,
client: MockClient((request) async {
requests++;
return http.Response('', 500);
}),
workerUrl: 'https://cleanup.test',
);
await expectLater(
service.deleteCloudData(),
throwsA(isA<StateError>().having(
(error) => error.message,
'message',
'A permanent account is required.',
)),
);
expect(requests, 0);
});
test('rejects invalid Worker configuration before making a request', () async {
final auth = _Auth();
addTearDown(auth.dispose);
final service = AccountCloudCleanupService(
auth: auth,
client: MockClient((request) async => http.Response('', 200)),
workerUrl: '',
);
await expectLater(
service.deleteCloudData(),
throwsA(isA<StateError>().having(
(error) => error.message,
'message',
'MotorCloud cleanup endpoint is unavailable.',
)),
);
});
}