11import 'dart:convert' ;
2+ import 'dart:typed_data' ;
23
34import 'package:http/http.dart' ;
45import 'package:supabase/supabase.dart' ;
@@ -50,6 +51,20 @@ import 'utils/filter_parser.dart';
5051/// }
5152/// ```
5253///
54+ /// You can mock edge functions using the [registerEdgeFunction] callback:
55+ /// ```dart
56+ /// final client = MockSupabaseHttpClient();
57+ /// client.registerEdgeFunction(
58+ /// 'get_user_status',
59+ /// (body, queryParameters, method, tables) {
60+ /// return FunctionResponse(
61+ /// data: {'status': 'active'},
62+ /// status: 200,
63+ /// );
64+ ///
65+ /// final response = await supabaseClient.functions.invoke('get_user_status');
66+ /// ```
67+ ///
5368/// You can simulate errors using the [postgrestExceptionTrigger] callback:
5469/// ```dart
5570/// final client = MockSupabaseHttpClient(
@@ -76,6 +91,14 @@ import 'utils/filter_parser.dart';
7691/// {@endtemplate}
7792class MockSupabaseHttpClient extends BaseClient {
7893 final Map <String , List <Map <String , dynamic >>> _database = {};
94+ final Map <
95+ String ,
96+ FunctionResponse Function (
97+ Map <String , dynamic > body,
98+ Map <String , String > queryParameters,
99+ HttpMethod method,
100+ Map <String , List <Map <String , dynamic >>> tables,
101+ )> _edgeFunctions = {};
79102 final Map <
80103 String ,
81104 dynamic Function (Map <String , dynamic >? params,
@@ -122,6 +145,7 @@ class MockSupabaseHttpClient extends BaseClient {
122145 // Clear the mock database and RPC functions
123146 _database.clear ();
124147 _rpcHandler.reset ();
148+ _edgeFunctions.clear ();
125149 }
126150
127151 /// Registers a RPC function that can be called using the `rpc` method on a `Postgrest` client.
@@ -206,6 +230,19 @@ class MockSupabaseHttpClient extends BaseClient {
206230
207231 @override
208232 Future <StreamedResponse > send (BaseRequest request) async {
233+ final functionName = _extractFunctionName (request.url);
234+ if (functionName != null ) {
235+ if (_edgeFunctions.containsKey (functionName)) {
236+ return _handleFunctionInvocation (functionName, request);
237+ } else {
238+ return _createResponse (
239+ {'error' : 'Function $functionName not found' },
240+ statusCode: 404 ,
241+ request: request,
242+ );
243+ }
244+ }
245+
209246 // Decode the request body if it's not a GET, DELETE, or HEAD request
210247 dynamic body;
211248 if (request.method != 'GET' &&
@@ -791,13 +828,125 @@ class MockSupabaseHttpClient extends BaseClient {
791828 'content-type' : 'application/json; charset=utf-8' ,
792829 ...? headers,
793830 };
831+ Stream <List <int >> stream;
832+ if (data is Uint8List ) {
833+ stream = Stream .value (data);
834+ responseHeaders['content-type' ] = _getContentType (data);
835+ } else if (data is String ) {
836+ stream = Stream .value (utf8.encode (data));
837+ responseHeaders['content-type' ] = _getContentType (data);
838+ } else {
839+ final jsonData = jsonEncode (data);
840+ stream = Stream .value (utf8.encode (jsonData));
841+ }
842+
794843 return StreamedResponse (
795- Stream . value (utf8. encode (data is String ? data : jsonEncode (data))) ,
844+ stream ,
796845 statusCode,
797846 headers: responseHeaders,
798847 request: request,
799848 );
800849 }
850+
851+ /// Registers an edge function with the given name and handler.
852+ ///
853+ /// The [name] parameter specifies the name of the edge function.
854+ ///
855+ /// The [handler] parameter is a function that takes the following parameters:
856+ /// - [body] : A map containing the body of the request.
857+ /// - [queryParameters] : A map containing the query parameters of the request.
858+ /// - [method] : The HTTP method of the request.
859+ /// - [tables] : A map containing lists of maps representing the tables involved in the request.
860+ ///
861+ /// The [handler] function should return a [FunctionResponse] .
862+ void registerEdgeFunction (
863+ String name,
864+ FunctionResponse Function (
865+ Map <String , dynamic > body,
866+ Map <String , String > queryParameters,
867+ HttpMethod method,
868+ Map <String , List <Map <String , dynamic >>> tables,
869+ ) handler,
870+ ) {
871+ _edgeFunctions[name] = handler;
872+ }
873+
874+ String ? _extractFunctionName (Uri url) {
875+ final pathSegments = url.pathSegments;
876+ // Handle functions endpoint: /functions/v1/{function_name}
877+ if (pathSegments.length >= 3 &&
878+ pathSegments[0 ] == 'functions' &&
879+ pathSegments[1 ] == 'v1' ) {
880+ return pathSegments[2 ];
881+ }
882+ return null ;
883+ }
884+
885+ Future <StreamedResponse > _handleFunctionInvocation (
886+ String functionName,
887+ BaseRequest request,
888+ ) async {
889+ if (! _edgeFunctions.containsKey (functionName)) {
890+ return _createResponse (
891+ {'error' : 'Edge function $functionName not found' },
892+ statusCode: 404 ,
893+ request: request,
894+ );
895+ }
896+
897+ // Parse request data
898+ final tables = _database;
899+ final body = await _parseRequestBody (request);
900+ final queryParams = request.url.queryParameters;
901+ final method = _parseMethod (request.method);
902+
903+ // Call handler
904+ final response = _edgeFunctions[functionName]! (
905+ body ?? {},
906+ queryParams,
907+ method,
908+ tables,
909+ );
910+
911+ return _createResponse (
912+ response.data,
913+ statusCode: response.status,
914+ request: request,
915+ headers: {
916+ 'content-type' : _getContentType (response.data),
917+ },
918+ );
919+ }
920+
921+ Future <dynamic > _parseRequestBody (BaseRequest request) async {
922+ if (request is ! Request ) return null ;
923+ final content = await request.finalize ().transform (utf8.decoder).join ();
924+ return content.isEmpty ? null : jsonDecode (content);
925+ }
926+
927+ HttpMethod _parseMethod (String method) {
928+ switch (method.toUpperCase ()) {
929+ case 'GET' :
930+ return HttpMethod .get ;
931+ case 'POST' :
932+ return HttpMethod .post;
933+ case 'PATCH' :
934+ return HttpMethod .patch;
935+ case 'DELETE' :
936+ return HttpMethod .delete;
937+ case 'PUT' :
938+ return HttpMethod .put;
939+ default :
940+ return HttpMethod .get ;
941+ }
942+ }
943+
944+ String _getContentType (dynamic data) {
945+ if (data is Uint8List ) return 'application/octet-stream' ;
946+ if (data is String ) return 'text/plain' ;
947+ if (data is Stream <List <int >>) return 'text/event-stream' ;
948+ return 'application/json' ;
949+ }
801950}
802951
803952/// Represents the different types of HTTP requests that can be made to the Supabase API
0 commit comments