Skip to content

Commit 5de2dd3

Browse files
committed
improve test coverage for supabase
1 parent 88ed5d8 commit 5de2dd3

File tree

3 files changed

+482
-0
lines changed

3 files changed

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

0 commit comments

Comments
 (0)