import "package:arcane_framework/arcane_framework.dart"; import "package:flutter/material.dart"; import "package:flutter_test/flutter_test.dart"; import "package:mocktail/mocktail.dart"; class MockArcaneAuthInterface extends Mock implements ArcaneAuthInterface {} class MockAccountRegistration extends Mock implements ArcaneAuthInterface, ArcaneAuthAccountRegistration {} class MockPasswordManagement extends Mock implements ArcaneAuthInterface, ArcaneAuthPasswordManagement {} void main() { group("ArcaneAuthenticationService error and edge cases", () { setUp(() async { await ArcaneAuthenticationService.I.reset(); Arcane.environment.reset(); }); test("reset clears interface and notifiers", () async { final auth = MockArcaneAuthInterface(); when(() => auth.init()).thenAnswer((_) async { return null; }); await ArcaneAuthenticationService.I.registerInterface(auth); await ArcaneAuthenticationService.I.reset(); expect(ArcaneAuthenticationService.I.authInterface, isNull); expect( ArcaneAuthenticationService.I.status, AuthenticationStatus.unknown, ); expect(ArcaneAuthenticationService.I.isSignedIn.value, false); }); test("registerInterface throws if already registered", () async { final auth = MockArcaneAuthInterface(); when(() => auth.init()).thenAnswer((_) async { return null; }); await ArcaneAuthenticationService.I.registerInterface(auth); expect( () async => ArcaneAuthenticationService.I.registerInterface(auth), throwsException, ); }); test("login returns error if no interface registered", () async { final result = await ArcaneAuthenticationService.I.login(input: {}); expect(result.isFailure, true); expect(result.error, contains("No ArcaneAuthInterface")); }); test("logOut returns error if no interface registered", () async { final result = await ArcaneAuthenticationService.I.logOut(); expect(result.isFailure, true); expect(result.error, contains("No ArcaneAuthInterface")); }); test("logOut returns error if not authenticated", () async { final auth = MockArcaneAuthInterface(); when(() => auth.init()).thenAnswer((_) async { return null; }); await ArcaneAuthenticationService.I.registerInterface(auth); final result = await ArcaneAuthenticationService.I.logOut(); expect(result.isFailure, true); expect(result.error, contains("not authenticated")); }); test("register returns error if no interface registered", () async { final result = await ArcaneAuthenticationService.I.register(input: {}); expect(result.isFailure, true); expect(result.error, contains("No ArcaneAuthInterface")); }); test("register returns error if interface does not support registration", () async { final auth = MockArcaneAuthInterface(); when(() => auth.init()).thenAnswer((_) async { return null; }); await ArcaneAuthenticationService.I.registerInterface(auth); final result = await ArcaneAuthenticationService.I.register(input: {}); expect(result.isFailure, true); expect(result.error, contains("does not support account registration")); }); test("register returns error if registration returns null", () async { final auth = MockAccountRegistration(); when(() => auth.init()).thenAnswer((_) async { return null; }); when(() => auth.register(input: any(named: "input"))) .thenAnswer((_) async => const Result.error("returned a null value")); await ArcaneAuthenticationService.I.registerInterface(auth); final result = await ArcaneAuthenticationService.I.register(input: {}); expect(result.isFailure, true); expect(result.error, contains("returned a null value")); }); test("confirmSignup returns error if no interface registered", () async { final result = await ArcaneAuthenticationService.I .confirmSignup(email: "a", confirmationCode: "b"); expect(result.isFailure, true); expect(result.error, contains("No ArcaneAuthInterface")); }); test( "confirmSignup returns error if interface does not support registration", () async { final auth = MockArcaneAuthInterface(); when(() => auth.init()).thenAnswer((_) async { return null; }); await ArcaneAuthenticationService.I.registerInterface(auth); final result = await ArcaneAuthenticationService.I .confirmSignup(email: "a", confirmationCode: "b"); expect(result.isFailure, true); expect(result.error, contains("does not support account registration")); }); test("confirmSignup returns error if confirmSignup returns null", () async { final auth = MockAccountRegistration(); when(() => auth.init()).thenAnswer((_) async { return null; }); when( () => auth.confirmSignup( username: any(named: "username"), confirmationCode: any(named: "confirmationCode"), ), ).thenAnswer((_) async => const Result.error("returned a null value")); await ArcaneAuthenticationService.I.registerInterface(auth); final result = await ArcaneAuthenticationService.I .confirmSignup(email: "a", confirmationCode: "b"); expect(result.isFailure, true); expect(result.error, contains("returned a null value")); }); test("resendVerificationCode returns error if no interface registered", () async { final result = await ArcaneAuthenticationService.I.resendVerificationCode("a"); expect(result.isFailure, true); expect(result.error, contains("No ArcaneAuthInterface")); }); test( "resendVerificationCode returns error if interface does not support registration", () async { final auth = MockArcaneAuthInterface(); when(() => auth.init()).thenAnswer((_) async { return null; }); await ArcaneAuthenticationService.I.registerInterface(auth); final result = await ArcaneAuthenticationService.I.resendVerificationCode("a"); expect(result.isFailure, true); expect(result.error, contains("does not support account registration")); }); test( "resendVerificationCode returns error if resendVerificationCode returns null", () async { final auth = MockAccountRegistration(); when(() => auth.init()).thenAnswer((_) async { return null; }); when(() => auth.resendVerificationCode(input: any(named: "input"))) .thenReturn(null); await ArcaneAuthenticationService.I.registerInterface(auth); final result = await ArcaneAuthenticationService.I.resendVerificationCode("a"); expect(result.isFailure, true); expect(result.error, contains("returned a null value")); }); test("resetPassword returns error if no interface registered", () async { final result = await ArcaneAuthenticationService.I.resetPassword(email: "a"); expect(result.isFailure, true); expect(result.error, contains("No ArcaneAuthInterface")); }); test( "resetPassword returns error if interface does not support password management", () async { final auth = MockArcaneAuthInterface(); when(() => auth.init()).thenAnswer((_) async { return null; }); await ArcaneAuthenticationService.I.registerInterface(auth); final result = await ArcaneAuthenticationService.I.resetPassword(email: "a"); expect(result.isFailure, true); expect(result.error, contains("does not support password management")); }); test("resetPassword returns error if resetPassword returns null", () async { final auth = MockPasswordManagement(); when(() => auth.init()).thenAnswer((_) async { return null; }); when( () => auth.resetPassword( email: any(named: "email"), newPassword: any(named: "newPassword"), code: any(named: "code"), ), ).thenAnswer((_) async => const Result.error("returned a null value")); await ArcaneAuthenticationService.I.registerInterface(auth); final result = await ArcaneAuthenticationService.I.resetPassword(email: "a"); expect(result.isFailure, true); expect(result.error, contains("returned a null value")); }); test("dispose closes stream controllers and calls super", () async { // Just call dispose to ensure no exceptions are thrown ArcaneAuthenticationService.I.dispose(); // No assertion needed; just ensure no crash }); test("notifier getter reflects unauthenticated default", () { expect( ArcaneAuthenticationService.I.notifier.value, AuthenticationStatus.unknown, ); }); test("isUnknown getter reflects unknown status", () { expect( ArcaneAuthenticationService.I.status.isUnknown, isTrue, ); }); test("isUnknown getter reflects unknown status after a reset", () async { expect( ArcaneAuthenticationService.I.status.isUnknown, isTrue, ); ArcaneAuthenticationService.I.setAuthenticated(); expect( ArcaneAuthenticationService.I.status.isUnknown, isFalse, ); expect( ArcaneAuthenticationService.I.status.isAuthenticated, isTrue, ); ArcaneAuthenticationService.I.setUnauthenticated(); expect( ArcaneAuthenticationService.I.status.isUnknown, isFalse, ); expect( ArcaneAuthenticationService.I.status.isAuthenticated, isFalse, ); expect( ArcaneAuthenticationService.I.status.isUnauthenticated, isTrue, ); await ArcaneAuthenticationService.I.reset(); expect( ArcaneAuthenticationService.I.status.isUnknown, isTrue, ); expect( ArcaneAuthenticationService.I.status.isAuthenticated, isFalse, ); expect( ArcaneAuthenticationService.I.status.isUnauthenticated, isTrue, ); }); test("accessToken and refreshToken fall back to empty string", () async { expect(await ArcaneAuthenticationService.I.accessToken, ""); expect(await ArcaneAuthenticationService.I.refreshToken, ""); }); testWidgets("setDebug and setNormal callbacks run on state transitions", (tester) async { late BuildContext capturedContext; var debugCallbackCalled = false; var normalCallbackCalled = false; await tester.pumpWidget( MaterialApp( home: Builder( builder: (context) { capturedContext = context; return const SizedBox(); }, ), ), ); await ArcaneAuthenticationService.I.setDebug( capturedContext, onDebugModeSet: () async { debugCallbackCalled = true; }, ); await ArcaneAuthenticationService.I.setNormal( capturedContext, onDebugModeUnset: () async { normalCallbackCalled = true; }, ); expect(debugCallbackCalled, isTrue); expect(normalCallbackCalled, isTrue); }); }); late ArcaneAuthInterface authInterface; group("ArcaneAuthenticationService", () { setUp(() async { authInterface = MockArcaneAuthInterface(); // Initialize the service await ArcaneAuthenticationService.I.reset(); Arcane.environment.reset(); when(() => authInterface.init()).thenAnswer((_) async {}); when( () => authInterface.login>( input: any(named: "input"), onLoggedIn: any(named: "onLoggedIn"), ), ).thenAnswer((_) async => const Result.ok(null)); when( () => authInterface.logout( onLoggedOut: any(named: "onLoggedOut"), ), ).thenAnswer((_) async => const Result.ok(null)); await ArcaneAuthenticationService.I.registerInterface(authInterface); }); testWidgets("login with success", (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( home: ArcaneEnvironmentProvider( environment: Environment.normal, child: Builder( builder: (context) { return Container(); }, ), ), ), ); final result = await ArcaneAuthenticationService.I.login( input: {"username": "test"}, ); expect(result.isSuccess, true); expect( ArcaneAuthenticationService.I.status, equals(AuthenticationStatus.authenticated), ); }); testWidgets("login with failure", (WidgetTester tester) async { when( () => authInterface.login>( input: any(named: "input"), onLoggedIn: any(named: "onLoggedIn"), ), ).thenAnswer((_) async => const Result.error("error")); final result = await ArcaneAuthenticationService.I .login(input: {"username": "test"}); expect(result.isFailure, true); expect( ArcaneAuthenticationService.I.status, equals(AuthenticationStatus.unknown), ); }); testWidgets("logout with success", (WidgetTester tester) async { ArcaneAuthenticationService.I.setAuthenticated(); final result = await ArcaneAuthenticationService.I.logOut(); expect(result.isSuccess, true); expect( ArcaneAuthenticationService.I.status, equals(AuthenticationStatus.unauthenticated), ); }); testWidgets("setDebug enables debug mode", (WidgetTester tester) async { late BuildContext capturedContext; await tester.pumpWidget( MaterialApp( home: ArcaneEnvironmentProvider( child: Builder( builder: (context) { capturedContext = context; return Container(); }, ), ), ), ); await tester.pump(); ArcaneEnvironment.of(capturedContext).enableDebugMode(); await tester.pump(); expect( ArcaneEnvironment.of(capturedContext).environment, equals(Environment.debug), ); }); testWidgets( "setDebug and setNormal use Arcane.environment without provider ancestry", (WidgetTester tester) async { late BuildContext capturedContext; await tester.pumpWidget( MaterialApp( home: Builder( builder: (context) { capturedContext = context; return Container(); }, ), ), ); await ArcaneAuthenticationService.I.setDebug(capturedContext); expect(Arcane.environment.current, Environment.debug); await ArcaneAuthenticationService.I.setNormal(capturedContext); expect(Arcane.environment.current, Environment.normal); }, ); testWidgets("setNormal disables debug mode", (WidgetTester tester) async { late BuildContext capturedContext; await tester.pumpWidget( MaterialApp( home: ArcaneEnvironmentProvider( child: Builder( builder: (context) { capturedContext = context; return Container(); }, ), ), ), ); await tester.pump(); ArcaneEnvironment.of(capturedContext).enableDebugMode(); await tester.pump(); expect( ArcaneEnvironment.of(capturedContext).environment, equals(Environment.debug), ); ArcaneEnvironment.of(capturedContext).disableDebugMode(); await tester.pump(); expect( ArcaneEnvironment.of(capturedContext).environment, equals(Environment.normal), ); }); testWidgets( "setDebug and setNormal do not mutate authentication status", (WidgetTester tester) async { late BuildContext capturedContext; await tester.pumpWidget( MaterialApp( home: ArcaneEnvironmentProvider( child: Builder( builder: (context) { capturedContext = context; return Container(); }, ), ), ), ); await ArcaneAuthenticationService.I.login( input: {"username": "test"}, ); expect(ArcaneAuthenticationService.I.isAuthenticated, true); await ArcaneAuthenticationService.I.setDebug(capturedContext); expect( ArcaneAuthenticationService.I.status, AuthenticationStatus.authenticated, ); await ArcaneAuthenticationService.I.setNormal(capturedContext); expect( ArcaneAuthenticationService.I.status, AuthenticationStatus.authenticated, ); }, ); testWidgets("supports custom environment values", (WidgetTester tester) async { late BuildContext capturedContext; const Environment staging = Environment("staging"); await tester.pumpWidget( MaterialApp( home: ArcaneEnvironmentProvider( child: Builder( builder: (context) { capturedContext = context; return Container(); }, ), ), ), ); ArcaneEnvironment.of(capturedContext).setEnvironment(staging); await tester.pump(); expect(ArcaneEnvironment.of(capturedContext).environment, staging); expect(ArcaneEnvironment.of(capturedContext).environment.name, "staging"); }); test("statusChanges emits authentication updates", () async { final statusEvent = expectLater( ArcaneAuthenticationService.I.statusChanges, emits(AuthenticationStatus.authenticated), ); ArcaneAuthenticationService.I.setAuthenticated(); await statusEvent; }); test("signedInChanges emits signed-in updates", () async { final signedInEvent = expectLater( ArcaneAuthenticationService.I.signedInChanges, emits(true), ); ArcaneAuthenticationService.I.setAuthenticated(); await signedInEvent; }); test("statusChanges works after listener cancellation", () async { final firstSubscription = ArcaneAuthenticationService.I.statusChanges.listen((_) {}); await firstSubscription.cancel(); // Ensure a deterministic baseline before asserting the next stream event. ArcaneAuthenticationService.I.setUnauthenticated(); final secondEvent = expectLater( ArcaneAuthenticationService.I.statusChanges, emits(AuthenticationStatus.authenticated), ); ArcaneAuthenticationService.I.setAuthenticated(); await secondEvent; }); test("statusChanges and signedInChanges stay coherent", () async { ArcaneAuthenticationService.I.setUnauthenticated(); final statusEvents = expectLater( ArcaneAuthenticationService.I.statusChanges, emitsInOrder( [ AuthenticationStatus.authenticated, AuthenticationStatus.unauthenticated, ], ), ); final signedInEvents = expectLater( ArcaneAuthenticationService.I.signedInChanges, emitsInOrder([true, false]), ); ArcaneAuthenticationService.I.setAuthenticated(); ArcaneAuthenticationService.I.setUnauthenticated(); await statusEvents; await signedInEvents; }); }); }