Skip to content

Commit 241bb18

Browse files
committed
chore: improve supabase_flutter coverage
1 parent 6a86752 commit 241bb18

File tree

5 files changed

+407
-1
lines changed

5 files changed

+407
-1
lines changed
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import 'package:flutter_test/flutter_test.dart';
2+
import 'package:supabase_flutter/supabase_flutter.dart';
3+
4+
import 'widget_test_stubs.dart';
5+
6+
class _MockLocalStorage extends MockLocalStorage {
7+
bool _initializeCalled = false;
8+
9+
bool get initializeCalled => _initializeCalled;
10+
11+
@override
12+
Future<void> initialize() async {
13+
_initializeCalled = true;
14+
return super.initialize();
15+
}
16+
}
17+
18+
void main() {
19+
TestWidgetsFlutterBinding.ensureInitialized();
20+
21+
const supabaseUrl = '';
22+
const supabaseKey = '';
23+
24+
group('Authentication', () {
25+
setUp(() async {
26+
try {
27+
await Supabase.instance.dispose();
28+
} catch (e) {
29+
// Ignore dispose errors
30+
}
31+
32+
mockAppLink();
33+
});
34+
35+
tearDown(() async {
36+
try {
37+
await Supabase.instance.dispose();
38+
} catch (e) {
39+
// Ignore dispose errors
40+
}
41+
});
42+
43+
group('Session management', () {
44+
test('initializes local storage on initialize', () async {
45+
final mockStorage = _MockLocalStorage();
46+
47+
await Supabase.initialize(
48+
url: supabaseUrl,
49+
anonKey: supabaseKey,
50+
debug: false,
51+
authOptions: FlutterAuthClientOptions(
52+
localStorage: mockStorage,
53+
pkceAsyncStorage: MockAsyncStorage(),
54+
),
55+
);
56+
57+
// Give time for initialization to complete
58+
await Future.delayed(const Duration(milliseconds: 100));
59+
60+
expect(mockStorage.initializeCalled, isTrue);
61+
});
62+
});
63+
64+
group('Session recovery', () {
65+
test('handles corrupted session data gracefully', () async {
66+
final corruptedStorage = MockExpiredStorage();
67+
68+
await Supabase.initialize(
69+
url: supabaseUrl,
70+
anonKey: supabaseKey,
71+
debug: false,
72+
authOptions: FlutterAuthClientOptions(
73+
localStorage: corruptedStorage,
74+
pkceAsyncStorage: MockAsyncStorage(),
75+
),
76+
);
77+
78+
// MockExpiredStorage returns an expired session, not null
79+
expect(Supabase.instance.client.auth.currentSession, isNotNull);
80+
expect(Supabase.instance.client.auth.currentSession?.isExpired, isTrue);
81+
});
82+
83+
test('handles null session during initialization', () async {
84+
final emptyStorage = MockEmptyLocalStorage();
85+
86+
await Supabase.initialize(
87+
url: supabaseUrl,
88+
anonKey: supabaseKey,
89+
debug: false,
90+
authOptions: FlutterAuthClientOptions(
91+
localStorage: emptyStorage,
92+
pkceAsyncStorage: MockAsyncStorage(),
93+
),
94+
);
95+
96+
// Should handle empty storage gracefully
97+
expect(Supabase.instance.client.auth.currentSession, isNull);
98+
});
99+
});
100+
});
101+
}

packages/supabase_flutter/test/deep_link_test.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,4 +69,4 @@ void main() {
6969
expect(pkceHttpClient.lastRequestBody['auth_code'], 'my-code-verifier');
7070
});
7171
});
72-
}
72+
}
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
import 'package:flutter_test/flutter_test.dart';
2+
import 'package:shared_preferences/shared_preferences.dart';
3+
import 'package:supabase_flutter/supabase_flutter.dart';
4+
5+
import 'widget_test_stubs.dart';
6+
7+
void main() {
8+
TestWidgetsFlutterBinding.ensureInitialized();
9+
10+
const supabaseUrl = '';
11+
const supabaseKey = '';
12+
13+
group('Supabase initialization', () {
14+
setUp(() {
15+
SharedPreferences.setMockInitialValues({});
16+
mockAppLink();
17+
});
18+
19+
tearDown(() async {
20+
try {
21+
await Supabase.instance.dispose();
22+
} catch (e) {
23+
// Ignore dispose errors
24+
}
25+
});
26+
27+
group('Basic initialization', () {
28+
test('initialize successfully with default options', () async {
29+
await Supabase.initialize(
30+
url: supabaseUrl,
31+
anonKey: supabaseKey,
32+
);
33+
34+
expect(Supabase.instance, isNotNull);
35+
expect(Supabase.instance.client, isNotNull);
36+
});
37+
38+
});
39+
40+
group('Custom storage initialization', () {
41+
test('initialize successfully with custom localStorage', () async {
42+
final localStorage = MockLocalStorage();
43+
await Supabase.initialize(
44+
url: supabaseUrl,
45+
anonKey: supabaseKey,
46+
authOptions: FlutterAuthClientOptions(
47+
localStorage: localStorage,
48+
),
49+
);
50+
51+
expect(Supabase.instance, isNotNull);
52+
expect(Supabase.instance.client, isNotNull);
53+
});
54+
55+
test('handles initialization with expired session in storage', () async {
56+
await Supabase.initialize(
57+
url: supabaseUrl,
58+
anonKey: supabaseKey,
59+
debug: true,
60+
authOptions: FlutterAuthClientOptions(
61+
localStorage: MockExpiredStorage(),
62+
pkceAsyncStorage: MockAsyncStorage(),
63+
),
64+
);
65+
66+
// Should handle expired session gracefully
67+
expect(Supabase.instance.client.auth.currentSession, isNotNull);
68+
});
69+
});
70+
71+
group('Auth options initialization', () {
72+
test('initialize successfully with PKCE auth flow', () async {
73+
await Supabase.initialize(
74+
url: supabaseUrl,
75+
anonKey: supabaseKey,
76+
authOptions: const FlutterAuthClientOptions(
77+
authFlowType: AuthFlowType.pkce,
78+
),
79+
);
80+
81+
expect(Supabase.instance, isNotNull);
82+
expect(Supabase.instance.client, isNotNull);
83+
});
84+
85+
86+
});
87+
88+
group('Custom client initialization', () {
89+
test('initialize successfully with custom HTTP client', () async {
90+
final httpClient = PkceHttpClient();
91+
await Supabase.initialize(
92+
url: supabaseUrl,
93+
anonKey: supabaseKey,
94+
httpClient: httpClient,
95+
);
96+
97+
expect(Supabase.instance, isNotNull);
98+
expect(Supabase.instance.client, isNotNull);
99+
});
100+
101+
test('initialize successfully with custom access token', () async {
102+
await Supabase.initialize(
103+
url: supabaseUrl,
104+
anonKey: supabaseKey,
105+
accessToken: () async => 'custom-access-token',
106+
);
107+
108+
expect(Supabase.instance, isNotNull);
109+
expect(Supabase.instance.client, isNotNull);
110+
111+
// Should throw AuthException when trying to access auth
112+
expect(
113+
() => Supabase.instance.client.auth,
114+
throwsA(isA<AuthException>()),
115+
);
116+
});
117+
});
118+
119+
group('Multiple initialization and disposal', () {
120+
test('dispose and reinitialize works', () async {
121+
// First initialization
122+
await Supabase.initialize(
123+
url: supabaseUrl,
124+
anonKey: supabaseKey,
125+
);
126+
127+
expect(Supabase.instance, isNotNull);
128+
129+
// Dispose
130+
await Supabase.instance.dispose();
131+
132+
// Need to run the event loop to let the dispose complete
133+
await Future.delayed(Duration.zero);
134+
135+
// Re-initialize should work without errors
136+
await Supabase.initialize(
137+
url: supabaseUrl,
138+
anonKey: supabaseKey,
139+
);
140+
141+
expect(Supabase.instance, isNotNull);
142+
});
143+
144+
test('handles multiple initializations correctly', () async {
145+
await Supabase.initialize(
146+
url: supabaseUrl,
147+
anonKey: supabaseKey,
148+
debug: false,
149+
authOptions: FlutterAuthClientOptions(
150+
localStorage: MockLocalStorage(),
151+
pkceAsyncStorage: MockAsyncStorage(),
152+
),
153+
);
154+
155+
// Store first instance to verify it's different after re-initialization
156+
final firstInstance = Supabase.instance.client;
157+
158+
// Dispose first instance before re-initializing
159+
await Supabase.instance.dispose();
160+
161+
await Supabase.initialize(
162+
url: supabaseUrl,
163+
anonKey: supabaseKey,
164+
debug: true,
165+
authOptions: FlutterAuthClientOptions(
166+
localStorage: MockEmptyLocalStorage(),
167+
pkceAsyncStorage: MockAsyncStorage(),
168+
),
169+
);
170+
171+
final secondInstance = Supabase.instance.client;
172+
expect(secondInstance, isNotNull);
173+
expect(identical(firstInstance, secondInstance), isFalse);
174+
});
175+
});
176+
});
177+
}
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import 'package:flutter_test/flutter_test.dart';
2+
import 'package:shared_preferences/shared_preferences.dart';
3+
import 'package:supabase_flutter/supabase_flutter.dart';
4+
5+
void main() {
6+
TestWidgetsFlutterBinding.ensureInitialized();
7+
8+
group('Storage Tests', () {
9+
// SharedPreferencesLocalStorage Tests
10+
group('SharedPreferencesLocalStorage', () {
11+
const persistSessionKey = 'test_persist_key';
12+
const testSessionValue = '{"key": "value"}';
13+
late SharedPreferencesLocalStorage localStorage;
14+
15+
setUp(() async {
16+
// Set up fake shared preferences
17+
SharedPreferences.setMockInitialValues({});
18+
localStorage = SharedPreferencesLocalStorage(
19+
persistSessionKey: persistSessionKey,
20+
);
21+
await localStorage.initialize();
22+
});
23+
24+
test('hasAccessToken returns false when no session exists', () async {
25+
final result = await localStorage.hasAccessToken();
26+
expect(result, false);
27+
});
28+
29+
test('hasAccessToken returns true when session exists', () async {
30+
await localStorage.persistSession(testSessionValue);
31+
final result = await localStorage.hasAccessToken();
32+
expect(result, true);
33+
});
34+
35+
test('accessToken returns null when no session exists', () async {
36+
final result = await localStorage.accessToken();
37+
expect(result, null);
38+
});
39+
40+
test('accessToken returns session string when session exists', () async {
41+
await localStorage.persistSession(testSessionValue);
42+
final result = await localStorage.accessToken();
43+
expect(result, testSessionValue);
44+
});
45+
46+
test('persistSession stores session string', () async {
47+
await localStorage.persistSession(testSessionValue);
48+
final prefs = await SharedPreferences.getInstance();
49+
final storedValue = prefs.getString(persistSessionKey);
50+
expect(storedValue, testSessionValue);
51+
});
52+
53+
test('removePersistedSession removes session', () async {
54+
// First store a session
55+
await localStorage.persistSession(testSessionValue);
56+
expect(await localStorage.hasAccessToken(), true);
57+
58+
// Then remove it
59+
await localStorage.removePersistedSession();
60+
expect(await localStorage.hasAccessToken(), false);
61+
expect(await localStorage.accessToken(), null);
62+
});
63+
});
64+
65+
// SharedPreferencesGotrueAsyncStorage Tests
66+
group('SharedPreferencesGotrueAsyncStorage', () {
67+
late SharedPreferencesGotrueAsyncStorage asyncStorage;
68+
const testKey = 'test_key';
69+
const testValue = 'test_value';
70+
71+
setUp(() async {
72+
// Set up fake shared preferences
73+
SharedPreferences.setMockInitialValues({});
74+
asyncStorage = SharedPreferencesGotrueAsyncStorage();
75+
// Allow for initialization to complete
76+
await Future.delayed(const Duration(milliseconds: 100));
77+
});
78+
79+
test('setItem stores value for key', () async {
80+
await asyncStorage.setItem(key: testKey, value: testValue);
81+
final prefs = await SharedPreferences.getInstance();
82+
final storedValue = prefs.getString(testKey);
83+
expect(storedValue, testValue);
84+
});
85+
86+
test('getItem returns null when no value exists', () async {
87+
final result = await asyncStorage.getItem(key: 'non_existent_key');
88+
expect(result, null);
89+
});
90+
91+
test('getItem returns value when value exists', () async {
92+
await asyncStorage.setItem(key: testKey, value: testValue);
93+
final result = await asyncStorage.getItem(key: testKey);
94+
expect(result, testValue);
95+
});
96+
97+
test('removeItem removes value', () async {
98+
// First store a value
99+
await asyncStorage.setItem(key: testKey, value: testValue);
100+
expect(await asyncStorage.getItem(key: testKey), testValue);
101+
102+
// Then remove it
103+
await asyncStorage.removeItem(key: testKey);
104+
expect(await asyncStorage.getItem(key: testKey), null);
105+
});
106+
});
107+
108+
});
109+
}

0 commit comments

Comments
 (0)