Skip to content

Commit ea69a08

Browse files
authored
chore(supabase): Improve test coverage for supabase (#1183)
* improve test coverage for supabase * ignore deprecated members in tests
1 parent 1be505c commit ea69a08

File tree

3 files changed

+486
-0
lines changed

3 files changed

+486
-0
lines changed

packages/supabase/test/client_test.dart

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,4 +282,208 @@ void main() {
282282
expect(xClientInfoHeader, 'supabase-flutter/0.0.0');
283283
});
284284
});
285+
286+
group('Client Advanced Features', () {
287+
late SupabaseClient supabase;
288+
const supabaseUrl = 'https://example.supabase.co';
289+
const supabaseKey = 'test-key';
290+
291+
setUp(() {
292+
supabase = SupabaseClient(supabaseUrl, supabaseKey);
293+
});
294+
295+
tearDown(() async {
296+
await supabase.dispose();
297+
});
298+
299+
group('Headers Management', () {
300+
test('should update headers and propagate to all clients', () {
301+
final newHeaders = {'Custom-Header': 'custom-value'};
302+
supabase.headers = newHeaders;
303+
304+
expect(supabase.headers['Custom-Header'], 'custom-value');
305+
expect(supabase.rest.headers['Custom-Header'], 'custom-value');
306+
expect(supabase.functions.headers['Custom-Header'], 'custom-value');
307+
expect(supabase.storage.headers['Custom-Header'], 'custom-value');
308+
expect(supabase.realtime.headers['Custom-Header'], 'custom-value');
309+
});
310+
311+
test('should preserve default headers when setting custom headers', () {
312+
final newHeaders = {'Custom-Header': 'custom-value'};
313+
supabase.headers = newHeaders;
314+
315+
expect(supabase.headers['X-Client-Info'], startsWith('supabase-dart/'));
316+
});
317+
318+
test('should not update auth headers when using custom access token', () {
319+
final customTokenClient = SupabaseClient(
320+
supabaseUrl,
321+
supabaseKey,
322+
accessToken: () async => 'custom-token',
323+
);
324+
325+
final newHeaders = {'Custom-Header': 'custom-value'};
326+
customTokenClient.headers = newHeaders;
327+
328+
expect(customTokenClient.headers['Custom-Header'], 'custom-value');
329+
});
330+
});
331+
332+
group('Error Handling', () {
333+
test(
334+
'should throw AuthException when accessing auth with custom access token',
335+
() {
336+
final customTokenClient = SupabaseClient(
337+
supabaseUrl,
338+
supabaseKey,
339+
accessToken: () async => 'custom-token',
340+
);
341+
342+
expect(
343+
() => customTokenClient.auth,
344+
throwsA(isA<AuthException>()),
345+
);
346+
});
347+
});
348+
349+
group('Schema Support', () {
350+
test('should create query builder with custom schema', () {
351+
final customSchema = supabase.schema('custom');
352+
expect(customSchema, isA<SupabaseQuerySchema>());
353+
354+
final queryBuilder = customSchema.from('table');
355+
expect(queryBuilder, isA<SupabaseQueryBuilder>());
356+
});
357+
358+
test('should handle nested schema calls', () {
359+
final schema1 = supabase.schema('schema1');
360+
final schema2 = schema1.schema('schema2');
361+
362+
expect(schema2, isA<SupabaseQuerySchema>());
363+
});
364+
});
365+
366+
group('RPC Support', () {
367+
test('should create RPC call', () {
368+
final rpcCall = supabase.rpc('test_function');
369+
expect(rpcCall, isA<PostgrestFilterBuilder>());
370+
});
371+
372+
test('should create RPC call with parameters', () {
373+
final rpcCall =
374+
supabase.rpc('test_function', params: {'param': 'value'});
375+
expect(rpcCall, isA<PostgrestFilterBuilder>());
376+
});
377+
378+
test('should create RPC call with get flag', () {
379+
final rpcCall = supabase.rpc('test_function', params: {}, get: true);
380+
expect(rpcCall, isA<PostgrestFilterBuilder>());
381+
});
382+
});
383+
384+
group('Client Options', () {
385+
test('should accept custom Postgrest options', () {
386+
final client = SupabaseClient(
387+
supabaseUrl,
388+
supabaseKey,
389+
postgrestOptions: PostgrestClientOptions(schema: 'custom_schema'),
390+
);
391+
392+
expect(client, isA<SupabaseClient>());
393+
});
394+
395+
test('should accept custom Auth options', () {
396+
final client = SupabaseClient(
397+
supabaseUrl,
398+
supabaseKey,
399+
authOptions: AuthClientOptions(autoRefreshToken: false),
400+
);
401+
402+
expect(client, isA<SupabaseClient>());
403+
});
404+
405+
test('should accept custom Storage options', () {
406+
final client = SupabaseClient(
407+
supabaseUrl,
408+
supabaseKey,
409+
storageOptions: StorageClientOptions(retryAttempts: 5),
410+
);
411+
412+
expect(client, isA<SupabaseClient>());
413+
});
414+
415+
test('should accept custom Realtime options', () {
416+
final client = SupabaseClient(
417+
supabaseUrl,
418+
supabaseKey,
419+
realtimeClientOptions:
420+
RealtimeClientOptions(logLevel: RealtimeLogLevel.debug),
421+
);
422+
423+
expect(client, isA<SupabaseClient>());
424+
});
425+
});
426+
427+
group('Dispose', () {
428+
test('should properly dispose all resources', () async {
429+
final client = SupabaseClient(supabaseUrl, supabaseKey);
430+
431+
// Should not throw
432+
await client.dispose();
433+
});
434+
});
435+
});
436+
437+
group('Query Schema', () {
438+
late SupabaseClient supabase;
439+
const supabaseUrl = 'https://example.supabase.co';
440+
const supabaseKey = 'test-key';
441+
442+
setUp(() {
443+
supabase = SupabaseClient(supabaseUrl, supabaseKey);
444+
});
445+
446+
tearDown(() async {
447+
await supabase.dispose();
448+
});
449+
450+
test('should create SupabaseQueryBuilder from schema', () {
451+
final schema = supabase.schema('custom_schema');
452+
final queryBuilder = schema.from('test_table');
453+
454+
expect(queryBuilder, isA<SupabaseQueryBuilder>());
455+
});
456+
457+
test('should create nested schemas', () {
458+
final schema1 = supabase.schema('schema1');
459+
final schema2 = schema1.schema('schema2');
460+
461+
expect(schema2, isA<SupabaseQuerySchema>());
462+
});
463+
});
464+
465+
group('Query Builder', () {
466+
late SupabaseClient supabase;
467+
const supabaseUrl = 'https://example.supabase.co';
468+
const supabaseKey = 'test-key';
469+
470+
setUp(() {
471+
supabase = SupabaseClient(supabaseUrl, supabaseKey);
472+
});
473+
474+
tearDown(() async {
475+
await supabase.dispose();
476+
});
477+
478+
group('Stream Creation', () {
479+
test('should throw assertion error for empty primary key', () {
480+
final queryBuilder = supabase.from('test_table');
481+
482+
expect(
483+
() => queryBuilder.stream(primaryKey: []),
484+
throwsA(isA<AssertionError>()),
485+
);
486+
});
487+
});
488+
});
285489
}

packages/supabase/test/mock_test.dart

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
// ignore_for_file: deprecated_member_use_from_same_package
2+
13
import 'dart:async';
24
import 'dart:convert';
35
import 'dart:io';
@@ -679,4 +681,118 @@ void main() {
679681
expect(stream, emits(isList));
680682
});
681683
});
684+
685+
group('Deprecated execute method', () {
686+
test('should work with deprecated execute method', () {
687+
handleRequests(mockServer);
688+
final streamBuilder = supabase.from('todos').stream(primaryKey: ['id']);
689+
final stream = streamBuilder.execute();
690+
expect(stream, emits(isList));
691+
});
692+
});
693+
694+
group('Error Handling', () {
695+
group('RealtimeSubscribeException', () {
696+
test('should create exception with status only', () {
697+
final exception =
698+
RealtimeSubscribeException(RealtimeSubscribeStatus.timedOut);
699+
700+
expect(exception.status, RealtimeSubscribeStatus.timedOut);
701+
expect(exception.details, isNull);
702+
expect(exception.toString(), contains('timedOut'));
703+
});
704+
705+
test('should create exception with status and details', () {
706+
final exception = RealtimeSubscribeException(
707+
RealtimeSubscribeStatus.channelError, 'Connection failed');
708+
709+
expect(exception.status, RealtimeSubscribeStatus.channelError);
710+
expect(exception.details, 'Connection failed');
711+
expect(exception.toString(), contains('channelError'));
712+
expect(exception.toString(), contains('Connection failed'));
713+
});
714+
});
715+
716+
group('Stream Error Handling', () {
717+
test('should handle postgrest errors gracefully', () async {
718+
final errorServer = await HttpServer.bind('localhost', 0);
719+
720+
// Setup server to return error for rest requests
721+
errorServer.listen((request) {
722+
if (request.uri.path.contains('/rest/')) {
723+
request.response
724+
..statusCode = HttpStatus.unauthorized
725+
..headers.contentType = ContentType.json
726+
..write('{"error": "Unauthorized"}')
727+
..close();
728+
} else {
729+
request.response
730+
..statusCode = HttpStatus.ok
731+
..close();
732+
}
733+
});
734+
735+
final errorClient = SupabaseClient(
736+
'http://${errorServer.address.host}:${errorServer.port}',
737+
'test-key',
738+
headers: {'X-Client-Info': 'supabase-flutter/0.0.0'},
739+
);
740+
741+
final stream = errorClient.from('todos').stream(primaryKey: ['id']);
742+
743+
bool errorReceived = false;
744+
final completer = Completer<void>();
745+
746+
final subscription = stream.listen(
747+
(_) {},
748+
onError: (error) {
749+
errorReceived = true;
750+
completer.complete();
751+
},
752+
);
753+
754+
await completer.future.timeout(Duration(seconds: 5));
755+
expect(errorReceived, isTrue);
756+
757+
await subscription.cancel();
758+
await errorClient.dispose();
759+
await errorServer.close();
760+
});
761+
762+
test('should handle access token retrieval errors', () async {
763+
final clientWithFailingToken = SupabaseClient(
764+
'http://${mockServer.address.host}:${mockServer.port}',
765+
'test-key',
766+
accessToken: () async {
767+
throw Exception('Token retrieval failed');
768+
},
769+
headers: {'X-Client-Info': 'supabase-flutter/0.0.0'},
770+
);
771+
772+
// Should handle token errors gracefully
773+
expect(
774+
() async => await clientWithFailingToken.from('test').select(),
775+
throwsA(isA<Exception>()),
776+
);
777+
778+
await clientWithFailingToken.dispose();
779+
});
780+
});
781+
782+
group('Dispose Error Handling', () {
783+
test('should handle dispose errors gracefully', () async {
784+
final client = SupabaseClient(
785+
'http://${mockServer.address.host}:${mockServer.port}',
786+
'test-key',
787+
headers: {'X-Client-Info': 'supabase-flutter/0.0.0'},
788+
);
789+
790+
// First dispose should succeed
791+
await client.dispose();
792+
793+
// Operations after dispose should not throw
794+
expect(() => client.from('test'), returnsNormally);
795+
});
796+
});
797+
});
682798
}

0 commit comments

Comments
 (0)