Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
81 changes: 81 additions & 0 deletions lib/src/mock_supabase_http_client.dart
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import 'dart:convert';
import 'dart:typed_data';

import 'package:http/http.dart';
import 'package:supabase/supabase.dart';

import 'handlers/rpc_handler.dart';
import 'utils/filter_parser.dart';

class MockSupabaseHttpClient extends BaseClient {
final Map<String, List<Map<String, dynamic>>> _database = {};
final Map<String, FunctionResponse> _registeredFunctions = {};
final Map<
String,
dynamic Function(Map<String, dynamic>? params,
Expand All @@ -22,6 +25,7 @@ class MockSupabaseHttpClient extends BaseClient {
// Clear the mock database and RPC functions
_database.clear();
_rpcHandler.reset();
_registeredFunctions.clear();
}

/// Registers a RPC function that can be called using the `rpc` method on a `Postgrest` client.
Expand Down Expand Up @@ -106,6 +110,19 @@ class MockSupabaseHttpClient extends BaseClient {

@override
Future<StreamedResponse> send(BaseRequest request) async {
final functionName = _extractFunctionName(request.url);
if (functionName != null) {
if (_registeredFunctions.containsKey(functionName)) {
return _handleFunctionInvocation(functionName, request);
} else {
return _createResponse(
{'error': 'Function $functionName not found'},
statusCode: 404,
request: request,
);
}
}

// Decode the request body if it's not a GET, DELETE, or HEAD request
dynamic body;
if (request.method != 'GET' &&
Expand Down Expand Up @@ -635,4 +652,68 @@ class MockSupabaseHttpClient extends BaseClient {
request: request,
);
}

/// Registers a response for a specific function name.
///
/// This method allows you to associate a [FunctionResponse] with a given
/// [functionName]. The registered response can later be retrieved or used
/// when the function is called.
///
/// - Parameters:
/// - functionName: The name of the function to register the response for.
/// - response: The [FunctionResponse] to be associated with the function name.
void registerFunctionResponse(
String functionName, FunctionResponse response) {
_registeredFunctions[functionName] = response;
}

String? _extractFunctionName(Uri url) {
final pathSegments = url.pathSegments;
// Handle functions endpoint: /functions/v1/{function_name}
if (pathSegments.length >= 3 &&
pathSegments[0] == 'functions' &&
pathSegments[1] == 'v1') {
return pathSegments[2];
}
return null;
}

StreamedResponse _handleFunctionInvocation(
String functionName,
BaseRequest request,
) {
final response = _registeredFunctions[functionName]!;
final statusCode = response.status;
final data = response.data;

Stream<List<int>> stream;
if (data is Stream<List<int>>) {
stream = data;
} else if (data is Uint8List) {
stream = Stream.value(data);
} else if (data is String) {
stream = Stream.value(utf8.encode(data));
} else {
final jsonString = jsonEncode(data);
stream = Stream.value(utf8.encode(jsonString));
}

final headers = {
'content-type': _getContentType(data),
};

return StreamedResponse(
stream,
statusCode,
headers: headers,
request: request,
);
}

String _getContentType(dynamic data) {
if (data is Uint8List) return 'application/octet-stream';
if (data is String) return 'text/plain';
if (data is Stream<List<int>>) return 'text/event-stream';
return 'application/json';
}
}
21 changes: 21 additions & 0 deletions test/mock_supabase_http_client_test.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import 'dart:async';
import 'dart:convert';

import 'package:mock_supabase_http_client/mock_supabase_http_client.dart';
import 'package:supabase/supabase.dart';
import 'package:test/test.dart';
Expand Down Expand Up @@ -1154,4 +1157,22 @@ void main() {
expect(customUser, containsPair('points', 50));
});
});

group("Edge functions", () {
test('Test function invocation', () async {
mockHttpClient.registerFunctionResponse(
'hello',
FunctionResponse(data: {'message': 'Hello'}, status: 200),
);

final response = await mockSupabase.functions.invoke('hello');
expect(response.data, {'message': 'Hello'});
expect(response.status, 200);
});

test("invocation failes if function not found", () async {
expect(() => mockSupabase.functions.invoke('non_existent_function'),
throwsA(isA<FunctionException>()));
});
});
}
Loading