Skip to content
This repository was archived by the owner on May 25, 2025. It is now read-only.

Commit a95cb82

Browse files
committed
feat: Add email link sign-in and refactor tests
- Added signInWithEmailLink method - Added sendSignInLinkToEmail method - Refactored tests for clarity - Improved exception handling
1 parent 1d5f0ee commit a95cb82

File tree

1 file changed

+137
-47
lines changed

1 file changed

+137
-47
lines changed

test/src/ht_authentication_repository_test.dart

Lines changed: 137 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,43 @@
11
import 'package:flutter_test/flutter_test.dart';
22
import 'package:ht_authentication_client/ht_authentication_client.dart';
3-
import 'package:ht_authentication_repository/src/ht_authentication_repository.dart';
3+
import 'package:ht_authentication_repository/ht_authentication_repository.dart'; // Import the public API
44
import 'package:mocktail/mocktail.dart';
55
import 'package:rxdart/rxdart.dart';
66

7+
// Mocks
78
class MockHtAuthenticationClient extends Mock
89
implements HtAuthenticationClient {}
910

11+
class MockUser extends Mock
12+
implements User {} // Add MockUser if needed for tests
13+
14+
// Helper to create exceptions with mock StackTrace
15+
AuthException _createException(
16+
Object error,
17+
AuthException Function(Object, StackTrace) constructor,
18+
) {
19+
return constructor(error, StackTrace.current);
20+
}
21+
1022
void main() {
1123
group('HtAuthenticationRepository', () {
1224
late HtAuthenticationClient client;
1325
late HtAuthenticationRepository repository;
26+
late BehaviorSubject<User> userSubject; // Declare userSubject here
1427

1528
setUp(() {
1629
client = MockHtAuthenticationClient();
17-
when(() => client.user).thenAnswer((_) => const Stream.empty());
30+
userSubject = BehaviorSubject<User>(); // Initialize userSubject
31+
// Mock client.user to return the stream from userSubject
32+
when(() => client.user).thenAnswer((_) => userSubject.stream);
1833
repository = HtAuthenticationRepository(authenticationClient: client);
1934
});
2035

36+
// Add tearDown to close the subject
37+
tearDown(() {
38+
userSubject.close();
39+
});
40+
2141
test('constructor', () {
2242
verify(() => client.user).called(1);
2343
});
@@ -28,75 +48,139 @@ void main() {
2848
});
2949

3050
test('emits new user when client emits', () {
31-
final user = User(uid: 'test-uid');
32-
final stream = BehaviorSubject<User>.seeded(user);
33-
when(() => client.user).thenAnswer((_) => stream);
34-
repository = HtAuthenticationRepository(authenticationClient: client);
51+
final user1 = MockUser();
52+
final user2 = User(uid: 'test-uid'); // Use a concrete User or MockUser
53+
54+
// Expect the stream from the repository (which gets from userSubject)
55+
expectLater(
56+
repository.user,
57+
// Seeded with default User(), then user1, then user2
58+
emitsInOrder(<Matcher>[isA<User>(), equals(user1), equals(user2)]),
59+
);
3560

36-
expect(repository.user, emitsInOrder([isA<User>(), user]));
61+
// Add users to the subject the repository is listening to
62+
userSubject
63+
..add(user1)
64+
..add(user2);
3765
});
3866
});
3967

4068
group('currentUser', () {
41-
test('returns current user', () {
42-
expect(repository.currentUser, isA<User>());
69+
// Renamed test for clarity
70+
test('reflects the latest user emitted by the stream', () async {
71+
final user = MockUser();
72+
// Optionally mock properties for better assertion
73+
when(() => user.uid).thenReturn('mock-user-id');
74+
75+
// Add the mock user to the stream the repository is listening to
76+
userSubject.add(user);
77+
78+
// Wait specifically for the 'user' we added to be emitted,
79+
// ignoring the initial default user.
80+
await expectLater(repository.user, emitsThrough(user));
81+
82+
// Now assert against the synchronous getter, which should be updated
83+
expect(repository.currentUser, equals(user));
84+
// Optionally assert properties
85+
expect(repository.currentUser.uid, equals('mock-user-id'));
4386
});
4487
});
4588

46-
group('signInWithEmailAndPassword', () {
89+
group('sendSignInLinkToEmail', () {
90+
const email = 'test@example.com';
91+
4792
test('calls client method', () async {
4893
when(
49-
() => client.signInWithEmailAndPassword(
50-
email: any(named: 'email'),
51-
password: any(named: 'password'),
52-
),
94+
() => client.sendSignInLinkToEmail(email: email),
5395
).thenAnswer((_) async {});
5496

55-
await repository.signInWithEmailAndPassword(
56-
email: 'test@example.com',
57-
password: 'password',
97+
await repository.sendSignInLinkToEmail(email: email);
98+
99+
verify(() => client.sendSignInLinkToEmail(email: email)).called(1);
100+
});
101+
102+
test('throws SendSignInLinkException on client error', () async {
103+
final exception = _createException(
104+
Exception(),
105+
SendSignInLinkException.new,
106+
);
107+
when(
108+
() => client.sendSignInLinkToEmail(email: email),
109+
).thenThrow(exception);
110+
111+
expect(
112+
() => repository.sendSignInLinkToEmail(email: email),
113+
throwsA(isA<SendSignInLinkException>()),
114+
);
115+
});
116+
117+
test('throws SendSignInLinkException on general error', () async {
118+
when(
119+
() => client.sendSignInLinkToEmail(email: email),
120+
).thenThrow(Exception());
121+
122+
expect(
123+
() => repository.sendSignInLinkToEmail(email: email),
124+
throwsA(isA<SendSignInLinkException>()),
58125
);
126+
});
127+
});
128+
129+
group('signInWithEmailLink', () {
130+
const email = 'test@example.com';
131+
const link = 'https://example.com/link';
132+
133+
test('calls client method', () async {
134+
when(
135+
() => client.signInWithEmailLink(email: email, emailLink: link),
136+
).thenAnswer((_) async {});
137+
138+
await repository.signInWithEmailLink(email: email, emailLink: link);
59139

60140
verify(
61-
() => client.signInWithEmailAndPassword(
62-
email: 'test@example.com',
63-
password: 'password',
64-
),
141+
() => client.signInWithEmailLink(email: email, emailLink: link),
65142
).called(1);
66143
});
67144

68-
test('throws EmailSignInException on client error', () async {
69-
final exception = EmailSignInException(Exception(), StackTrace.empty);
145+
test('throws InvalidSignInLinkException on client error', () async {
146+
final exception = _createException(
147+
Exception(),
148+
InvalidSignInLinkException.new,
149+
);
70150
when(
71-
() => client.signInWithEmailAndPassword(
72-
email: any(named: 'email'),
73-
password: any(named: 'password'),
74-
),
151+
() => client.signInWithEmailLink(email: email, emailLink: link),
75152
).thenThrow(exception);
76153

77154
expect(
78-
() => repository.signInWithEmailAndPassword(
79-
email: 'test@example.com',
80-
password: 'password',
81-
),
82-
throwsA(isA<EmailSignInException>()),
155+
() => repository.signInWithEmailLink(email: email, emailLink: link),
156+
throwsA(isA<InvalidSignInLinkException>()),
83157
);
84158
});
85159

86-
test('throws EmailSignInException on general error', () async {
160+
test('throws UserNotFoundException on client error', () async {
161+
final exception = _createException(
162+
Exception(),
163+
UserNotFoundException.new,
164+
);
87165
when(
88-
() => client.signInWithEmailAndPassword(
89-
email: any(named: 'email'),
90-
password: any(named: 'password'),
91-
),
166+
() => client.signInWithEmailLink(email: email, emailLink: link),
167+
).thenThrow(exception);
168+
169+
expect(
170+
() => repository.signInWithEmailLink(email: email, emailLink: link),
171+
throwsA(isA<UserNotFoundException>()),
172+
);
173+
});
174+
175+
test('throws InvalidSignInLinkException on general error', () async {
176+
when(
177+
() => client.signInWithEmailLink(email: email, emailLink: link),
92178
).thenThrow(Exception());
93179

180+
// Repository wraps general errors into InvalidSignInLinkException
94181
expect(
95-
() => repository.signInWithEmailAndPassword(
96-
email: 'test@example.com',
97-
password: 'password',
98-
),
99-
throwsA(isA<EmailSignInException>()),
182+
() => repository.signInWithEmailLink(email: email, emailLink: link),
183+
throwsA(isA<InvalidSignInLinkException>()),
100184
);
101185
});
102186
});
@@ -111,7 +195,10 @@ void main() {
111195
});
112196

113197
test('throws GoogleSignInException on client error', () async {
114-
final exception = GoogleSignInException(Exception(), StackTrace.empty);
198+
final exception = _createException(
199+
Exception(),
200+
GoogleSignInException.new,
201+
);
115202
when(() => client.signInWithGoogle()).thenThrow(exception);
116203

117204
expect(
@@ -140,9 +227,9 @@ void main() {
140227
});
141228

142229
test('throws AnonymousLoginException on client error', () async {
143-
final exception = AnonymousLoginException(
230+
final exception = _createException(
144231
Exception(),
145-
StackTrace.empty,
232+
AnonymousLoginException.new,
146233
);
147234
when(() => client.signInAnonymously()).thenThrow(exception);
148235

@@ -172,7 +259,7 @@ void main() {
172259
});
173260

174261
test('throws LogoutException on client error', () async {
175-
final exception = LogoutException(Exception(), StackTrace.empty);
262+
final exception = _createException(Exception(), LogoutException.new);
176263
when(() => client.signOut()).thenThrow(exception);
177264

178265
expect(() => repository.signOut(), throwsA(isA<LogoutException>()));
@@ -195,7 +282,10 @@ void main() {
195282
});
196283

197284
test('throws DeleteAccountException on client error', () async {
198-
final exception = DeleteAccountException(Exception(), StackTrace.empty);
285+
final exception = _createException(
286+
Exception(),
287+
DeleteAccountException.new,
288+
);
199289
when(() => client.deleteAccount()).thenThrow(exception);
200290

201291
expect(

0 commit comments

Comments
 (0)