Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
204 changes: 204 additions & 0 deletions packages/supabase/test/client_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -282,4 +282,208 @@ void main() {
expect(xClientInfoHeader, 'supabase-flutter/0.0.0');
});
});

group('Client Advanced Features', () {
late SupabaseClient supabase;
const supabaseUrl = 'https://example.supabase.co';
const supabaseKey = 'test-key';

setUp(() {
supabase = SupabaseClient(supabaseUrl, supabaseKey);
});

tearDown(() async {
await supabase.dispose();
});

group('Headers Management', () {
test('should update headers and propagate to all clients', () {
final newHeaders = {'Custom-Header': 'custom-value'};
supabase.headers = newHeaders;

expect(supabase.headers['Custom-Header'], 'custom-value');
expect(supabase.rest.headers['Custom-Header'], 'custom-value');
expect(supabase.functions.headers['Custom-Header'], 'custom-value');
expect(supabase.storage.headers['Custom-Header'], 'custom-value');
expect(supabase.realtime.headers['Custom-Header'], 'custom-value');
});

test('should preserve default headers when setting custom headers', () {
final newHeaders = {'Custom-Header': 'custom-value'};
supabase.headers = newHeaders;

expect(supabase.headers['X-Client-Info'], startsWith('supabase-dart/'));
});

test('should not update auth headers when using custom access token', () {
final customTokenClient = SupabaseClient(
supabaseUrl,
supabaseKey,
accessToken: () async => 'custom-token',
);

final newHeaders = {'Custom-Header': 'custom-value'};
customTokenClient.headers = newHeaders;

expect(customTokenClient.headers['Custom-Header'], 'custom-value');
});
});

group('Error Handling', () {
test(
'should throw AuthException when accessing auth with custom access token',
() {
final customTokenClient = SupabaseClient(
supabaseUrl,
supabaseKey,
accessToken: () async => 'custom-token',
);

expect(
() => customTokenClient.auth,
throwsA(isA<AuthException>()),
);
});
});

group('Schema Support', () {
test('should create query builder with custom schema', () {
final customSchema = supabase.schema('custom');
expect(customSchema, isA<SupabaseQuerySchema>());

final queryBuilder = customSchema.from('table');
expect(queryBuilder, isA<SupabaseQueryBuilder>());
});

test('should handle nested schema calls', () {
final schema1 = supabase.schema('schema1');
final schema2 = schema1.schema('schema2');

expect(schema2, isA<SupabaseQuerySchema>());
});
});

group('RPC Support', () {
test('should create RPC call', () {
final rpcCall = supabase.rpc('test_function');
expect(rpcCall, isA<PostgrestFilterBuilder>());
});

test('should create RPC call with parameters', () {
final rpcCall =
supabase.rpc('test_function', params: {'param': 'value'});
expect(rpcCall, isA<PostgrestFilterBuilder>());
});

test('should create RPC call with get flag', () {
final rpcCall = supabase.rpc('test_function', params: {}, get: true);
expect(rpcCall, isA<PostgrestFilterBuilder>());
});
});

group('Client Options', () {
test('should accept custom Postgrest options', () {
final client = SupabaseClient(
supabaseUrl,
supabaseKey,
postgrestOptions: PostgrestClientOptions(schema: 'custom_schema'),
);

expect(client, isA<SupabaseClient>());
});

test('should accept custom Auth options', () {
final client = SupabaseClient(
supabaseUrl,
supabaseKey,
authOptions: AuthClientOptions(autoRefreshToken: false),
);

expect(client, isA<SupabaseClient>());
});

test('should accept custom Storage options', () {
final client = SupabaseClient(
supabaseUrl,
supabaseKey,
storageOptions: StorageClientOptions(retryAttempts: 5),
);

expect(client, isA<SupabaseClient>());
});

test('should accept custom Realtime options', () {
final client = SupabaseClient(
supabaseUrl,
supabaseKey,
realtimeClientOptions:
RealtimeClientOptions(logLevel: RealtimeLogLevel.debug),
);

expect(client, isA<SupabaseClient>());
});
});

group('Dispose', () {
test('should properly dispose all resources', () async {
final client = SupabaseClient(supabaseUrl, supabaseKey);

// Should not throw
await client.dispose();
});
});
});

group('Query Schema', () {
late SupabaseClient supabase;
const supabaseUrl = 'https://example.supabase.co';
const supabaseKey = 'test-key';

setUp(() {
supabase = SupabaseClient(supabaseUrl, supabaseKey);
});

tearDown(() async {
await supabase.dispose();
});

test('should create SupabaseQueryBuilder from schema', () {
final schema = supabase.schema('custom_schema');
final queryBuilder = schema.from('test_table');

expect(queryBuilder, isA<SupabaseQueryBuilder>());
});

test('should create nested schemas', () {
final schema1 = supabase.schema('schema1');
final schema2 = schema1.schema('schema2');

expect(schema2, isA<SupabaseQuerySchema>());
});
});

group('Query Builder', () {
late SupabaseClient supabase;
const supabaseUrl = 'https://example.supabase.co';
const supabaseKey = 'test-key';

setUp(() {
supabase = SupabaseClient(supabaseUrl, supabaseKey);
});

tearDown(() async {
await supabase.dispose();
});

group('Stream Creation', () {
test('should throw assertion error for empty primary key', () {
final queryBuilder = supabase.from('test_table');

expect(
() => queryBuilder.stream(primaryKey: []),
throwsA(isA<AssertionError>()),
);
});
});
});
}
116 changes: 116 additions & 0 deletions packages/supabase/test/mock_test.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// ignore_for_file: deprecated_member_use_from_same_package

import 'dart:async';
import 'dart:convert';
import 'dart:io';
Expand Down Expand Up @@ -679,4 +681,118 @@ void main() {
expect(stream, emits(isList));
});
});

group('Deprecated execute method', () {
test('should work with deprecated execute method', () {
handleRequests(mockServer);
final streamBuilder = supabase.from('todos').stream(primaryKey: ['id']);
final stream = streamBuilder.execute();
expect(stream, emits(isList));
});
});

group('Error Handling', () {
group('RealtimeSubscribeException', () {
test('should create exception with status only', () {
final exception =
RealtimeSubscribeException(RealtimeSubscribeStatus.timedOut);

expect(exception.status, RealtimeSubscribeStatus.timedOut);
expect(exception.details, isNull);
expect(exception.toString(), contains('timedOut'));
});

test('should create exception with status and details', () {
final exception = RealtimeSubscribeException(
RealtimeSubscribeStatus.channelError, 'Connection failed');

expect(exception.status, RealtimeSubscribeStatus.channelError);
expect(exception.details, 'Connection failed');
expect(exception.toString(), contains('channelError'));
expect(exception.toString(), contains('Connection failed'));
});
});

group('Stream Error Handling', () {
test('should handle postgrest errors gracefully', () async {
final errorServer = await HttpServer.bind('localhost', 0);

// Setup server to return error for rest requests
errorServer.listen((request) {
if (request.uri.path.contains('/rest/')) {
request.response
..statusCode = HttpStatus.unauthorized
..headers.contentType = ContentType.json
..write('{"error": "Unauthorized"}')
..close();
} else {
request.response
..statusCode = HttpStatus.ok
..close();
}
});

final errorClient = SupabaseClient(
'http://${errorServer.address.host}:${errorServer.port}',
'test-key',
headers: {'X-Client-Info': 'supabase-flutter/0.0.0'},
);

final stream = errorClient.from('todos').stream(primaryKey: ['id']);

bool errorReceived = false;
final completer = Completer<void>();

final subscription = stream.listen(
(_) {},
onError: (error) {
errorReceived = true;
completer.complete();
},
);

await completer.future.timeout(Duration(seconds: 5));
expect(errorReceived, isTrue);

await subscription.cancel();
await errorClient.dispose();
await errorServer.close();
});

test('should handle access token retrieval errors', () async {
final clientWithFailingToken = SupabaseClient(
'http://${mockServer.address.host}:${mockServer.port}',
'test-key',
accessToken: () async {
throw Exception('Token retrieval failed');
},
headers: {'X-Client-Info': 'supabase-flutter/0.0.0'},
);

// Should handle token errors gracefully
expect(
() async => await clientWithFailingToken.from('test').select(),
throwsA(isA<Exception>()),
);

await clientWithFailingToken.dispose();
});
});

group('Dispose Error Handling', () {
test('should handle dispose errors gracefully', () async {
final client = SupabaseClient(
'http://${mockServer.address.host}:${mockServer.port}',
'test-key',
headers: {'X-Client-Info': 'supabase-flutter/0.0.0'},
);

// First dispose should succeed
await client.dispose();

// Operations after dispose should not throw
expect(() => client.from('test'), returnsNormally);
});
});
});
}
Loading