1
1
import 'dart:convert' ;
2
+ import 'dart:typed_data' ;
2
3
3
4
import 'package:http/http.dart' ;
4
5
import 'package:supabase/supabase.dart' ;
@@ -50,6 +51,20 @@ import 'utils/filter_parser.dart';
50
51
/// }
51
52
/// ```
52
53
///
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
+ ///
53
68
/// You can simulate errors using the [postgrestExceptionTrigger] callback:
54
69
/// ```dart
55
70
/// final client = MockSupabaseHttpClient(
@@ -76,6 +91,14 @@ import 'utils/filter_parser.dart';
76
91
/// {@endtemplate}
77
92
class MockSupabaseHttpClient extends BaseClient {
78
93
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 = {};
79
102
final Map <
80
103
String ,
81
104
dynamic Function (Map <String , dynamic >? params,
@@ -122,6 +145,7 @@ class MockSupabaseHttpClient extends BaseClient {
122
145
// Clear the mock database and RPC functions
123
146
_database.clear ();
124
147
_rpcHandler.reset ();
148
+ _edgeFunctions.clear ();
125
149
}
126
150
127
151
/// Registers a RPC function that can be called using the `rpc` method on a `Postgrest` client.
@@ -206,6 +230,19 @@ class MockSupabaseHttpClient extends BaseClient {
206
230
207
231
@override
208
232
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
+
209
246
// Decode the request body if it's not a GET, DELETE, or HEAD request
210
247
dynamic body;
211
248
if (request.method != 'GET' &&
@@ -791,13 +828,125 @@ class MockSupabaseHttpClient extends BaseClient {
791
828
'content-type' : 'application/json; charset=utf-8' ,
792
829
...? headers,
793
830
};
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
+
794
843
return StreamedResponse (
795
- Stream . value (utf8. encode (data is String ? data : jsonEncode (data))) ,
844
+ stream ,
796
845
statusCode,
797
846
headers: responseHeaders,
798
847
request: request,
799
848
);
800
849
}
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
+ }
801
950
}
802
951
803
952
/// Represents the different types of HTTP requests that can be made to the Supabase API
0 commit comments