kell_creations/kell_creations_apps/packages/auth/test/auth_test.dart

353 lines
12 KiB
Dart

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<AuthStateUnauthenticated>());
expect(service.currentUser, isNull);
});
test('authStateStream emits current state immediately', () async {
final first = await service.authStateStream.first;
expect(first, isA<AuthStateUnauthenticated>());
});
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<AuthStateAuthenticated>());
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<AuthException>()));
expect(service.currentState, isA<AuthStateError>());
});
test('signIn with empty password throws AuthException', () async {
await expectLater(
service.signIn(email: 'user@example.com', password: ''),
throwsA(isA<AuthException>()),
);
expect(service.currentState, isA<AuthStateError>());
});
test('signOut sets state to unauthenticated', () async {
await service.signIn(email: 'admin@kellcreations.com', password: 'pw');
await service.signOut();
expect(service.currentState, isA<AuthStateUnauthenticated>());
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 = <AuthState>[];
final sub = service.authStateStream.listen(states.add);
await service.signIn(email: 'admin@kellcreations.com', password: 'pw');
// Allow any pending microtasks to flush.
await Future<void>.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<AuthStateAuthenticated>());
});
test('authStateStream emits loading then unauthenticated on signOut', () async {
await service.signIn(email: 'admin@kellcreations.com', password: 'pw');
final states = <AuthState>[];
final sub = service.authStateStream.listen(states.add);
await service.signOut();
// Allow any pending microtasks to flush.
await Future<void>.delayed(Duration.zero);
await sub.cancel();
expect(states.last, isA<AuthStateUnauthenticated>());
});
});
}