import 'package:flutter_test/flutter_test.dart'; import 'package:auth/auth.dart'; void main() { // --------------------------------------------------------------------------- // KcUser // --------------------------------------------------------------------------- group('KcUser', () { const user = KcUser( id: 'u1', displayName: 'Alice', email: 'alice@example.com', role: KcUserRole.operator, ); test('stores all fields', () { expect(user.id, 'u1'); expect(user.displayName, 'Alice'); expect(user.email, 'alice@example.com'); expect(user.role, KcUserRole.operator); }); test('copyWith replaces specified fields', () { final updated = user.copyWith(displayName: 'Bob', role: KcUserRole.admin); expect(updated.id, 'u1'); expect(updated.displayName, 'Bob'); expect(updated.email, 'alice@example.com'); expect(updated.role, KcUserRole.admin); }); test('copyWith with no args returns equivalent user', () { final copy = user.copyWith(); expect(copy, equals(user)); }); test('equality compares all fields', () { const same = KcUser( id: 'u1', displayName: 'Alice', email: 'alice@example.com', role: KcUserRole.operator, ); const different = KcUser( id: 'u2', displayName: 'Alice', email: 'alice@example.com', role: KcUserRole.operator, ); expect(user, equals(same)); expect(user, isNot(equals(different))); expect(user.hashCode, equals(same.hashCode)); }); test('toString includes key fields', () { expect(user.toString(), contains('u1')); expect(user.toString(), contains('Alice')); expect(user.toString(), contains('operator')); }); }); // --------------------------------------------------------------------------- // KcUserRole // --------------------------------------------------------------------------- group('KcUserRole', () { test('has three values', () { expect(KcUserRole.values.length, 3); }); test('values are admin, operator, viewer', () { expect( KcUserRole.values, containsAll([KcUserRole.admin, KcUserRole.operator, KcUserRole.viewer]), ); }); }); // --------------------------------------------------------------------------- // AuthState // --------------------------------------------------------------------------- group('AuthState', () { const user = KcUser( id: 'u1', displayName: 'Alice', email: 'alice@example.com', role: KcUserRole.admin, ); test('unauthenticated: isAuthenticated is false', () { const state = AuthStateUnauthenticated(); expect(state.isAuthenticated, isFalse); expect(state.isLoading, isFalse); expect(state.user, isNull); }); test('loading: isLoading is true', () { const state = AuthStateLoading(); expect(state.isLoading, isTrue); expect(state.isAuthenticated, isFalse); expect(state.user, isNull); }); test('authenticated: isAuthenticated is true and user is set', () { final state = AuthStateAuthenticated(user: user); expect(state.isAuthenticated, isTrue); expect(state.isLoading, isFalse); expect(state.user, equals(user)); }); test('authenticated: equality compares user', () { final a = AuthStateAuthenticated(user: user); final b = AuthStateAuthenticated(user: user); const c = KcUser( id: 'u2', displayName: 'Bob', email: 'bob@example.com', role: KcUserRole.viewer, ); final d = AuthStateAuthenticated(user: c); expect(a, equals(b)); expect(a, isNot(equals(d))); expect(a.hashCode, equals(b.hashCode)); }); test('error: message is stored', () { const state = AuthStateError(message: 'Invalid credentials'); expect(state.isAuthenticated, isFalse); expect(state.user, isNull); expect(state.message, 'Invalid credentials'); }); test('error: equality compares message', () { const a = AuthStateError(message: 'oops'); const b = AuthStateError(message: 'oops'); const c = AuthStateError(message: 'different'); expect(a, equals(b)); expect(a, isNot(equals(c))); }); test('toString includes state type', () { expect(const AuthStateUnauthenticated().toString(), contains('unauthenticated')); expect(const AuthStateLoading().toString(), contains('loading')); expect(AuthStateAuthenticated(user: user).toString(), contains('authenticated')); expect(const AuthStateError(message: 'x').toString(), contains('error')); }); }); // --------------------------------------------------------------------------- // AuthException // --------------------------------------------------------------------------- group('AuthException', () { test('stores message and optional statusCode', () { const ex = AuthException(message: 'Unauthorized', statusCode: 401); expect(ex.message, 'Unauthorized'); expect(ex.statusCode, 401); }); test('toString includes message', () { const ex = AuthException(message: 'Forbidden'); expect(ex.toString(), contains('Forbidden')); }); test('toString includes HTTP status when present', () { const ex = AuthException(message: 'Unauthorized', statusCode: 401); expect(ex.toString(), contains('401')); }); test('statusCode is optional', () { const ex = AuthException(message: 'Network error'); expect(ex.statusCode, isNull); }); }); // --------------------------------------------------------------------------- // CredentialStore / CredentialKeys // --------------------------------------------------------------------------- group('InMemoryCredentialStore', () { late InMemoryCredentialStore store; setUp(() => store = InMemoryCredentialStore()); test('read returns null for missing key', () async { expect(await store.read('missing'), isNull); }); test('write and read round-trip', () async { await store.write(CredentialKeys.wcConsumerKey, 'ck_test'); expect(await store.read(CredentialKeys.wcConsumerKey), 'ck_test'); }); test('write overwrites existing value', () async { await store.write('key', 'first'); await store.write('key', 'second'); expect(await store.read('key'), 'second'); }); test('delete removes key', () async { await store.write('key', 'value'); await store.delete('key'); expect(await store.read('key'), isNull); }); test('delete is no-op for missing key', () async { await expectLater(store.delete('missing'), completes); }); test('deleteAll clears all keys', () async { await store.write('a', '1'); await store.write('b', '2'); await store.deleteAll(); expect(await store.read('a'), isNull); expect(await store.read('b'), isNull); }); test('containsKey returns true for present key', () async { await store.write('key', 'value'); expect(await store.containsKey('key'), isTrue); }); test('containsKey returns false for missing key', () async { expect(await store.containsKey('missing'), isFalse); }); }); group('CredentialKeys', () { test('all keys are non-empty strings', () { expect(CredentialKeys.wcConsumerKey, isNotEmpty); expect(CredentialKeys.wcConsumerSecret, isNotEmpty); expect(CredentialKeys.wcSiteUrl, isNotEmpty); expect(CredentialKeys.squareAccessToken, isNotEmpty); expect(CredentialKeys.squareLocationId, isNotEmpty); expect(CredentialKeys.sessionToken, isNotEmpty); }); test('all keys are unique', () { final keys = [ CredentialKeys.wcConsumerKey, CredentialKeys.wcConsumerSecret, CredentialKeys.wcSiteUrl, CredentialKeys.squareAccessToken, CredentialKeys.squareLocationId, CredentialKeys.sessionToken, ]; expect(keys.toSet().length, keys.length); }); }); // --------------------------------------------------------------------------- // FakeAuthService // --------------------------------------------------------------------------- group('FakeAuthService', () { late FakeAuthService service; setUp(() => service = FakeAuthService()); tearDown(() => service.dispose()); test('initial state is unauthenticated', () { expect(service.currentState, isA()); expect(service.currentUser, isNull); }); test('authStateStream emits current state immediately', () async { final first = await service.authStateStream.first; expect(first, isA()); }); test('signIn with valid credentials returns user', () async { final user = await service.signIn(email: 'operator@kellcreations.com', password: 'secret'); expect(user.email, 'operator@kellcreations.com'); expect(user.role, KcUserRole.operator); }); test('signIn sets state to authenticated', () async { await service.signIn(email: 'admin@kellcreations.com', password: 'pw'); expect(service.currentState, isA()); expect(service.currentUser?.role, KcUserRole.admin); }); test('signIn with admin email assigns admin role', () async { final user = await service.signIn(email: 'admin@kellcreations.com', password: 'pw'); expect(user.role, KcUserRole.admin); }); test('signIn with operator email assigns operator role', () async { final user = await service.signIn(email: 'operator@kellcreations.com', password: 'pw'); expect(user.role, KcUserRole.operator); }); test('signIn with unknown email assigns viewer role', () async { final user = await service.signIn(email: 'someone@example.com', password: 'pw'); expect(user.role, KcUserRole.viewer); }); test('signIn with empty email throws AuthException', () async { await expectLater(service.signIn(email: '', password: 'pw'), throwsA(isA())); expect(service.currentState, isA()); }); test('signIn with empty password throws AuthException', () async { await expectLater( service.signIn(email: 'user@example.com', password: ''), throwsA(isA()), ); expect(service.currentState, isA()); }); test('signOut sets state to unauthenticated', () async { await service.signIn(email: 'admin@kellcreations.com', password: 'pw'); await service.signOut(); expect(service.currentState, isA()); expect(service.currentUser, isNull); }); test('refreshSession returns true when authenticated', () async { await service.signIn(email: 'admin@kellcreations.com', password: 'pw'); expect(await service.refreshSession(), isTrue); }); test('refreshSession returns false when unauthenticated', () async { expect(await service.refreshSession(), isFalse); }); test('authStateStream emits loading then authenticated on signIn', () async { final states = []; final sub = service.authStateStream.listen(states.add); await service.signIn(email: 'admin@kellcreations.com', password: 'pw'); // Allow any pending microtasks to flush. await Future.delayed(Duration.zero); await sub.cancel(); // Stream: unauthenticated (initial), loading, authenticated expect(states.length, greaterThanOrEqualTo(2)); expect(states.any((s) => s is AuthStateLoading), isTrue); expect(states.last, isA()); }); test('authStateStream emits loading then unauthenticated on signOut', () async { await service.signIn(email: 'admin@kellcreations.com', password: 'pw'); final states = []; final sub = service.authStateStream.listen(states.add); await service.signOut(); // Allow any pending microtasks to flush. await Future.delayed(Duration.zero); await sub.cancel(); expect(states.last, isA()); }); }); }