@_nazmiforreal/flutter-ota 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dart-src/packages/cli-tools/.dart_tool/package_config.json +496 -0
- package/dart-src/packages/cli-tools/.dart_tool/package_graph.json +743 -0
- package/dart-src/packages/cli-tools/lib/src/cli_base.dart +7 -6
- package/dart-src/packages/cli-tools/lib/src/commands/build.dart +46 -49
- package/dart-src/packages/cli-tools/lib/src/commands/bundle.dart +44 -23
- package/dart-src/packages/cli-tools/lib/src/commands/channel.dart +27 -17
- package/dart-src/packages/cli-tools/lib/src/commands/console.dart +11 -6
- package/dart-src/packages/cli-tools/lib/src/commands/deploy.dart +39 -22
- package/dart-src/packages/cli-tools/lib/src/commands/doctor.dart +22 -13
- package/dart-src/packages/cli-tools/lib/src/commands/init.dart +117 -36
- package/dart-src/packages/cli-tools/lib/src/commands/keys.dart +15 -9
- package/dart-src/packages/cli-tools/lib/src/commands/migrate.dart +14 -8
- package/dart-src/packages/cli-tools/lib/src/commands/rollback.dart +37 -12
- package/dart-src/packages/cli-tools/lib/src/config.dart +13 -3
- package/dart-src/packages/cli-tools/lib/src/operations.dart +45 -0
- package/dart-src/packages/cli-tools/lib/src/pack.dart +9 -0
- package/dart-src/packages/cli-tools/lib/src/ui/ui.dart +234 -0
- package/dart-src/packages/cli-tools/pubspec.lock +638 -0
- package/dart-src/packages/cli-tools/test/backends_test.dart +207 -0
- package/dart-src/packages/cli-tools/test/build_pack_test.dart +101 -0
- package/dart-src/packages/cli-tools/test/cli_test.dart +442 -0
- package/dart-src/packages/cli-tools/test/mocks/mock_aws_s3_client.dart +83 -0
- package/dart-src/packages/cli-tools/test/mocks/mock_d1_client.dart +256 -0
- package/dart-src/packages/cli-tools/test/mocks/mock_postgres_client.dart +347 -0
- package/dart-src/packages/cli-tools/test/standalone_backend_test.dart +315 -0
- package/dart-src/packages/core/test/rollout_test.dart +128 -0
- package/dart-src/packages/core/test/semver_test.dart +52 -0
- package/dart-src/packages/core/test/semver_vectors.json +1 -0
- package/dart-src/packages/core/test/types_test.dart +129 -0
- package/dart-src/packages/core/test/uuid_test.dart +38 -0
- package/dart-src/plugins/aws/test/aws_database_test.dart +167 -0
- package/dart-src/plugins/aws/test/aws_lambda_edge_storage_test.dart +190 -0
- package/dart-src/plugins/aws/test/aws_storage_test.dart +73 -0
- package/dart-src/plugins/aws/test/mock_aws_s3_client.dart +83 -0
- package/dart-src/plugins/cloudflare/test/cloudflare_worker_database_test.dart +122 -0
- package/dart-src/plugins/cloudflare/test/d1_bundle_mapper_test.dart +106 -0
- package/dart-src/plugins/cloudflare/test/d1_database_test.dart +190 -0
- package/dart-src/plugins/cloudflare/test/mock_d1_client.dart +256 -0
- package/dart-src/plugins/cloudflare/test/mock_r2_client.dart +83 -0
- package/dart-src/plugins/cloudflare/test/r2_storage_test.dart +77 -0
- package/dart-src/plugins/plugin-core/test/asset_storage_layout_test.dart +166 -0
- package/dart-src/plugins/plugin-core/test/bundle_unit_of_work_store_test.dart +74 -0
- package/dart-src/plugins/plugin-core/test/bundle_unit_of_work_test.dart +332 -0
- package/dart-src/plugins/plugin-core/test/create_blob_database_plugin_test.dart +711 -0
- package/dart-src/plugins/plugin-core/test/create_database_plugin_test.dart +294 -0
- package/dart-src/plugins/plugin-core/test/create_storage_plugin_test.dart +192 -0
- package/dart-src/plugins/plugin-core/test/generate_min_bundle_id_test.dart +51 -0
- package/dart-src/plugins/plugin-core/test/get_update_info_test.dart +1600 -0
- package/dart-src/plugins/plugin-core/test/helpers_test.dart +259 -0
- package/dart-src/plugins/plugin-core/test/query_bundles_test.dart +139 -0
- package/dart-src/plugins/plugin-core/test/request_update_bundle_state_test.dart +86 -0
- package/dart-src/plugins/plugin-core/test/uuidv7_test.dart +77 -0
- package/dart-src/plugins/postgres/test/mock_postgres_client.dart +297 -0
- package/dart-src/plugins/postgres/test/postgres_bundle_mapper_test.dart +140 -0
- package/dart-src/plugins/postgres/test/postgres_database_test.dart +181 -0
- package/dart-src/plugins/standalone/test/standalone_test.dart +264 -0
- package/dart-src/plugins/supabase/test/mock_supabase_client.dart +654 -0
- package/dart-src/plugins/supabase/test/supabase_bundle_mapper_test.dart +112 -0
- package/dart-src/plugins/supabase/test/supabase_database_test.dart +207 -0
- package/dart-src/plugins/supabase/test/supabase_storage_test.dart +175 -0
- package/package.json +1 -1
- package/scripts/vendor.sh +29 -0
|
@@ -0,0 +1,654 @@
|
|
|
1
|
+
/// Faithful mock of the Supabase client surface, mirroring hot-updater's
|
|
2
|
+
/// `vi.mock("@supabase/supabase-js")` test seam.
|
|
3
|
+
///
|
|
4
|
+
/// The mock keeps an in-memory set of `bundles` / `bundle_patches` rows and
|
|
5
|
+
/// implements the PostgREST query-builder + RPC contract used by the database
|
|
6
|
+
/// plugin, plus a configurable storage bucket used by the storage plugin.
|
|
7
|
+
library;
|
|
8
|
+
|
|
9
|
+
import 'package:flutter_patcher_core/flutter_patcher_core.dart';
|
|
10
|
+
import 'package:flutter_patcher_plugin_core/flutter_patcher_plugin_core.dart';
|
|
11
|
+
|
|
12
|
+
import 'package:flutter_patcher_supabase/src/supabase_bundle_mapper.dart';
|
|
13
|
+
import 'package:flutter_patcher_supabase/src/supabase_client_adapter.dart';
|
|
14
|
+
import 'package:flutter_patcher_supabase/src/types.dart';
|
|
15
|
+
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
// In-memory row store
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
|
|
20
|
+
class Store {
|
|
21
|
+
final Map<String, Map<String, dynamic>> bundleRows = {};
|
|
22
|
+
final Map<String, Map<String, dynamic>> bundlePatchRows = {};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
// Query builder mock
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
|
|
29
|
+
sealed class _Filter {
|
|
30
|
+
const _Filter();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
class _EqFilter extends _Filter {
|
|
34
|
+
final String column;
|
|
35
|
+
final Object? value;
|
|
36
|
+
_EqFilter(this.column, this.value);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
class _CompareFilter extends _Filter {
|
|
40
|
+
final String type; // gt | gte | lt | lte
|
|
41
|
+
final String column;
|
|
42
|
+
final Object? value;
|
|
43
|
+
_CompareFilter(this.type, this.column, this.value);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
class _InFilter extends _Filter {
|
|
47
|
+
final String column;
|
|
48
|
+
final List<Object?> values;
|
|
49
|
+
_InFilter(this.column, this.values);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
class _IsFilter extends _Filter {
|
|
53
|
+
final String column;
|
|
54
|
+
final Object? value;
|
|
55
|
+
_IsFilter(this.column, this.value);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
class _NotFilter extends _Filter {
|
|
59
|
+
final String column;
|
|
60
|
+
final String operator;
|
|
61
|
+
final Object? value;
|
|
62
|
+
_NotFilter(this.column, this.operator, this.value);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
int _compareValues(Object? left, Object? right) {
|
|
66
|
+
if (left is String && right is String) return left.compareTo(right);
|
|
67
|
+
if (left is num && right is num) return left.compareTo(right);
|
|
68
|
+
if (left is bool && right is bool) {
|
|
69
|
+
return (left ? 1 : 0).compareTo(right ? 1 : 0);
|
|
70
|
+
}
|
|
71
|
+
return left.toString().compareTo(right.toString());
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
class _MockFilterBuilder implements SupabaseFilterBuilderLike {
|
|
75
|
+
_MockFilterBuilder(
|
|
76
|
+
this._store,
|
|
77
|
+
this._table,
|
|
78
|
+
this._mode, {
|
|
79
|
+
this.head = false,
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
final Store _store;
|
|
83
|
+
final String _table;
|
|
84
|
+
final String _mode;
|
|
85
|
+
final bool head;
|
|
86
|
+
final bool count = false;
|
|
87
|
+
|
|
88
|
+
final List<_Filter> _filters = [];
|
|
89
|
+
bool ascending = true;
|
|
90
|
+
int? limitValue;
|
|
91
|
+
int? rangeStart;
|
|
92
|
+
int? rangeEnd;
|
|
93
|
+
bool singleRow = false;
|
|
94
|
+
|
|
95
|
+
@override
|
|
96
|
+
SupabaseFilterBuilderLike eq(String column, Object? value) {
|
|
97
|
+
_filters.add(_EqFilter(column, value));
|
|
98
|
+
return this;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
@override
|
|
102
|
+
SupabaseFilterBuilderLike gt(String column, Object? value) {
|
|
103
|
+
_filters.add(_CompareFilter('gt', column, value));
|
|
104
|
+
return this;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
@override
|
|
108
|
+
SupabaseFilterBuilderLike gte(String column, Object? value) {
|
|
109
|
+
_filters.add(_CompareFilter('gte', column, value));
|
|
110
|
+
return this;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
@override
|
|
114
|
+
SupabaseFilterBuilderLike lt(String column, Object? value) {
|
|
115
|
+
_filters.add(_CompareFilter('lt', column, value));
|
|
116
|
+
return this;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
@override
|
|
120
|
+
SupabaseFilterBuilderLike lte(String column, Object? value) {
|
|
121
|
+
_filters.add(_CompareFilter('lte', column, value));
|
|
122
|
+
return this;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
@override
|
|
126
|
+
SupabaseFilterBuilderLike in_(String column, List<Object?> values) {
|
|
127
|
+
_filters.add(_InFilter(column, values));
|
|
128
|
+
return this;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
@override
|
|
132
|
+
SupabaseFilterBuilderLike isFilter(String column, Object? value) {
|
|
133
|
+
_filters.add(_IsFilter(column, value));
|
|
134
|
+
return this;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
@override
|
|
138
|
+
SupabaseFilterBuilderLike not(String column, String operator, Object? value) {
|
|
139
|
+
_filters.add(_NotFilter(column, operator, value));
|
|
140
|
+
return this;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
@override
|
|
144
|
+
SupabaseFilterBuilderLike order(String column, {bool? ascending}) {
|
|
145
|
+
this.ascending = ascending ?? true;
|
|
146
|
+
return this;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
@override
|
|
150
|
+
SupabaseFilterBuilderLike limit(int value) {
|
|
151
|
+
limitValue = value;
|
|
152
|
+
return this;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
@override
|
|
156
|
+
SupabaseFilterBuilderLike range(int from, int to) {
|
|
157
|
+
rangeStart = from;
|
|
158
|
+
rangeEnd = to;
|
|
159
|
+
return this;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
@override
|
|
163
|
+
SupabaseFilterBuilderLike single() {
|
|
164
|
+
singleRow = true;
|
|
165
|
+
return this;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
List<Map<String, dynamic>> _rowsForTable() =>
|
|
169
|
+
_table == 'bundles' ? _store.bundleRows.values.toList()
|
|
170
|
+
: _store.bundlePatchRows.values.toList();
|
|
171
|
+
|
|
172
|
+
List<Map<String, dynamic>> _getFilteredRows() {
|
|
173
|
+
var rows = _rowsForTable();
|
|
174
|
+
for (final filter in _filters) {
|
|
175
|
+
rows = rows.where((row) {
|
|
176
|
+
return switch (filter) {
|
|
177
|
+
_EqFilter(:final column, :final value) => row[column] == value,
|
|
178
|
+
_CompareFilter(:final type, :final column, :final value) =>
|
|
179
|
+
switch (type) {
|
|
180
|
+
'gt' => _compareValues(row[column], value) > 0,
|
|
181
|
+
'gte' => _compareValues(row[column], value) >= 0,
|
|
182
|
+
'lt' => _compareValues(row[column], value) < 0,
|
|
183
|
+
'lte' => _compareValues(row[column], value) <= 0,
|
|
184
|
+
_ => false,
|
|
185
|
+
},
|
|
186
|
+
_InFilter(:final column, :final values) =>
|
|
187
|
+
values.contains(row[column]),
|
|
188
|
+
_IsFilter(:final column, :final value) => row[column] == value,
|
|
189
|
+
_NotFilter(:final column, :final operator, :final value) =>
|
|
190
|
+
operator == 'is' ? row[column] != value : false,
|
|
191
|
+
};
|
|
192
|
+
}).toList();
|
|
193
|
+
}
|
|
194
|
+
return rows;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
@override
|
|
198
|
+
Future<SupabaseResponseLike> execute() async {
|
|
199
|
+
if (_mode == 'delete') {
|
|
200
|
+
final filtered = _getFilteredRows();
|
|
201
|
+
for (final row in filtered) {
|
|
202
|
+
if (_table == 'bundles') {
|
|
203
|
+
_store.bundleRows.remove(row['id']);
|
|
204
|
+
} else {
|
|
205
|
+
_store.bundlePatchRows.remove(row['id']);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return _MockResponse(data: null, error: null);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
var filtered = _getFilteredRows();
|
|
212
|
+
filtered = filtered.toList()
|
|
213
|
+
..sort((a, b) =>
|
|
214
|
+
ascending ? a['id'].compareTo(b['id']) : b['id'].compareTo(a['id']));
|
|
215
|
+
final total = filtered.length;
|
|
216
|
+
|
|
217
|
+
if (singleRow) {
|
|
218
|
+
final data = filtered.isEmpty ? null : filtered.first;
|
|
219
|
+
return _MockResponse(
|
|
220
|
+
data: data,
|
|
221
|
+
error: data == null ? {'message': 'Row not found'} : null,
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (head || count) {
|
|
226
|
+
return _MockResponse(data: null, error: null, count: total);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (rangeStart != null && rangeEnd != null) {
|
|
230
|
+
filtered = filtered.sublist(
|
|
231
|
+
rangeStart!,
|
|
232
|
+
(rangeEnd! + 1).clamp(0, filtered.length),
|
|
233
|
+
);
|
|
234
|
+
} else if (limitValue != null && limitValue! > 0) {
|
|
235
|
+
filtered = filtered.sublist(0, limitValue!.clamp(0, filtered.length));
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return _MockResponse(data: filtered, error: null, count: total);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// ---------------------------------------------------------------------------
|
|
243
|
+
// Storage bucket mock
|
|
244
|
+
// ---------------------------------------------------------------------------
|
|
245
|
+
|
|
246
|
+
class _ExistsResult implements SupabaseExistsResult {
|
|
247
|
+
_ExistsResult({this.data, this.error});
|
|
248
|
+
@override
|
|
249
|
+
final bool? data;
|
|
250
|
+
@override
|
|
251
|
+
final Object? error;
|
|
252
|
+
@override
|
|
253
|
+
final String? message = null;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
class _SignedUrlResult implements SupabaseSignedUrlResult {
|
|
257
|
+
_SignedUrlResult({this.signedUrl, this.error});
|
|
258
|
+
@override
|
|
259
|
+
final String? signedUrl;
|
|
260
|
+
@override
|
|
261
|
+
final Object? error;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
class _SignedUrlListResult implements SupabaseSignedUrlListResult {
|
|
265
|
+
_SignedUrlListResult({this.data, this.error});
|
|
266
|
+
@override
|
|
267
|
+
final List<SupabaseSignedUrlResult>? data;
|
|
268
|
+
@override
|
|
269
|
+
final Object? error;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
class _UploadResult implements SupabaseUploadResult {
|
|
273
|
+
_UploadResult({this.data, this.error});
|
|
274
|
+
@override
|
|
275
|
+
final Object? data;
|
|
276
|
+
@override
|
|
277
|
+
final Object? error;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
class _RemoveResult implements SupabaseRemoveResult {
|
|
281
|
+
_RemoveResult({this.data, this.error, this.message});
|
|
282
|
+
@override
|
|
283
|
+
final Object? data;
|
|
284
|
+
@override
|
|
285
|
+
final Object? error;
|
|
286
|
+
@override
|
|
287
|
+
final String? message;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
class _DownloadResult implements SupabaseDownloadResult {
|
|
291
|
+
_DownloadResult({this.data, this.error, this.message});
|
|
292
|
+
@override
|
|
293
|
+
final List<int>? data;
|
|
294
|
+
@override
|
|
295
|
+
final Object? error;
|
|
296
|
+
@override
|
|
297
|
+
final String? message;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
class _ListResult implements SupabaseListResult {
|
|
301
|
+
_ListResult({this.data, this.error});
|
|
302
|
+
@override
|
|
303
|
+
final List<SupabaseStorageObject>? data;
|
|
304
|
+
@override
|
|
305
|
+
final Object? error;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/// Configurable in-memory storage bucket.
|
|
309
|
+
class FakeStorageBucket implements SupabaseStorageBucketLike {
|
|
310
|
+
FakeStorageBucket({
|
|
311
|
+
this.existsData,
|
|
312
|
+
this.existsError,
|
|
313
|
+
this.existsThrows,
|
|
314
|
+
this.signedUrlData,
|
|
315
|
+
this.signedUrlError,
|
|
316
|
+
this.uploadData,
|
|
317
|
+
this.uploadError,
|
|
318
|
+
this.removeError,
|
|
319
|
+
this.removeMessage,
|
|
320
|
+
this.downloadData,
|
|
321
|
+
this.downloadError,
|
|
322
|
+
this.downloadMessage,
|
|
323
|
+
this.listData,
|
|
324
|
+
this.listError,
|
|
325
|
+
this.createSignedUrlBase = 'https://example.supabase.co',
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
bool? existsData;
|
|
329
|
+
Object? existsError;
|
|
330
|
+
Object? existsThrows;
|
|
331
|
+
String? signedUrlData;
|
|
332
|
+
Object? signedUrlError;
|
|
333
|
+
Object? uploadData;
|
|
334
|
+
Object? uploadError;
|
|
335
|
+
Object? removeError;
|
|
336
|
+
String? removeMessage;
|
|
337
|
+
List<int>? downloadData;
|
|
338
|
+
Object? downloadError;
|
|
339
|
+
String? downloadMessage;
|
|
340
|
+
List<SupabaseStorageObject>? listData;
|
|
341
|
+
Object? listError;
|
|
342
|
+
final String createSignedUrlBase;
|
|
343
|
+
|
|
344
|
+
final List<String> existsCalls = [];
|
|
345
|
+
final List<String> createSignedUrlCalls = [];
|
|
346
|
+
final List<List<String>> createSignedUrlsCalls = [];
|
|
347
|
+
final List<String> uploadCalls = [];
|
|
348
|
+
final List<List<String>> removeCalls = [];
|
|
349
|
+
final List<String> downloadCalls = [];
|
|
350
|
+
final List<String> listCalls = [];
|
|
351
|
+
|
|
352
|
+
@override
|
|
353
|
+
Future<SupabaseExistsResult> exists(String path) {
|
|
354
|
+
existsCalls.add(path);
|
|
355
|
+
if (existsThrows != null) {
|
|
356
|
+
return Future.error(existsThrows!);
|
|
357
|
+
}
|
|
358
|
+
return Future.value(
|
|
359
|
+
_ExistsResult(data: existsData, error: existsError),
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
@override
|
|
364
|
+
Future<SupabaseSignedUrlResult> createSignedUrl(String path, int expiresIn) {
|
|
365
|
+
createSignedUrlCalls.add(path);
|
|
366
|
+
return Future.value(
|
|
367
|
+
_SignedUrlResult(
|
|
368
|
+
signedUrl: signedUrlData ?? '$createSignedUrlBase/signed-url',
|
|
369
|
+
error: signedUrlError,
|
|
370
|
+
),
|
|
371
|
+
);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
@override
|
|
375
|
+
Future<SupabaseSignedUrlListResult> createSignedUrls(
|
|
376
|
+
List<String> paths,
|
|
377
|
+
int expiresIn,
|
|
378
|
+
) {
|
|
379
|
+
createSignedUrlsCalls.add(List.of(paths));
|
|
380
|
+
return Future.value(
|
|
381
|
+
_SignedUrlListResult(
|
|
382
|
+
data: paths
|
|
383
|
+
.map((p) => _SignedUrlResult(
|
|
384
|
+
signedUrl: '$createSignedUrlBase/$expiresIn/$p',
|
|
385
|
+
error: signedUrlError,
|
|
386
|
+
))
|
|
387
|
+
.toList(),
|
|
388
|
+
error: signedUrlError,
|
|
389
|
+
),
|
|
390
|
+
);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
@override
|
|
394
|
+
Future<SupabaseUploadResult> upload(
|
|
395
|
+
String path,
|
|
396
|
+
List<int> fileBytes, {
|
|
397
|
+
String? contentType,
|
|
398
|
+
String? cacheControl,
|
|
399
|
+
}) {
|
|
400
|
+
uploadCalls.add(path);
|
|
401
|
+
return Future.value(
|
|
402
|
+
_UploadResult(
|
|
403
|
+
data: uploadData ?? {'fullPath': path},
|
|
404
|
+
error: uploadError,
|
|
405
|
+
),
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
@override
|
|
410
|
+
Future<SupabaseRemoveResult> remove(List<String> paths) {
|
|
411
|
+
removeCalls.add(List.of(paths));
|
|
412
|
+
return Future.value(
|
|
413
|
+
_RemoveResult(data: [], error: removeError, message: removeMessage),
|
|
414
|
+
);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
@override
|
|
418
|
+
Future<SupabaseDownloadResult> download(String path) {
|
|
419
|
+
downloadCalls.add(path);
|
|
420
|
+
return Future.value(
|
|
421
|
+
_DownloadResult(
|
|
422
|
+
data: downloadData,
|
|
423
|
+
error: downloadError,
|
|
424
|
+
message: downloadMessage,
|
|
425
|
+
),
|
|
426
|
+
);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
@override
|
|
430
|
+
Future<SupabaseListResult> list([String? prefix]) {
|
|
431
|
+
listCalls.add(prefix ?? '');
|
|
432
|
+
return Future.value(_ListResult(data: listData, error: listError));
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// ---------------------------------------------------------------------------
|
|
437
|
+
// Top-level mock client
|
|
438
|
+
// ---------------------------------------------------------------------------
|
|
439
|
+
|
|
440
|
+
class _MockStorageClient implements SupabaseStorageClientLike {
|
|
441
|
+
_MockStorageClient(this.bucket);
|
|
442
|
+
final SupabaseStorageBucketLike bucket;
|
|
443
|
+
|
|
444
|
+
@override
|
|
445
|
+
SupabaseStorageBucketLike from(String bucketName) => bucket;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
class _MockQueryBuilder implements SupabaseQueryBuilderLike {
|
|
449
|
+
_MockQueryBuilder(this._store, this._table);
|
|
450
|
+
final Store _store;
|
|
451
|
+
final String _table;
|
|
452
|
+
|
|
453
|
+
@override
|
|
454
|
+
SupabaseFilterBuilderLike select(
|
|
455
|
+
String columns, {
|
|
456
|
+
bool? count,
|
|
457
|
+
bool? head,
|
|
458
|
+
}) =>
|
|
459
|
+
_MockFilterBuilder(_store, _table, 'select', head: head ?? false);
|
|
460
|
+
|
|
461
|
+
@override
|
|
462
|
+
SupabaseFilterBuilderLike delete() =>
|
|
463
|
+
_MockFilterBuilder(_store, _table, 'delete');
|
|
464
|
+
|
|
465
|
+
@override
|
|
466
|
+
Future<dynamic> upsert(dynamic values, {String? onConflict}) async {
|
|
467
|
+
final list = values is List ? values : [values];
|
|
468
|
+
for (final value in list) {
|
|
469
|
+
final Map<String, dynamic> map;
|
|
470
|
+
if (value is SupabaseBundleRow) {
|
|
471
|
+
map = value.toJson();
|
|
472
|
+
} else if (value is SupabaseBundlePatchRow) {
|
|
473
|
+
map = value.toJson();
|
|
474
|
+
} else {
|
|
475
|
+
map = Map<String, dynamic>.from(value as Map);
|
|
476
|
+
}
|
|
477
|
+
if (_table == 'bundles') {
|
|
478
|
+
_store.bundleRows[map['id'] as String] = map;
|
|
479
|
+
} else {
|
|
480
|
+
_store.bundlePatchRows[map['id'] as String] = map;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
return _MockResponse(data: null, error: null);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
class _MockResponse implements SupabaseResponseLike {
|
|
488
|
+
_MockResponse({this.data, this.error, this.count});
|
|
489
|
+
@override
|
|
490
|
+
final Object? data;
|
|
491
|
+
@override
|
|
492
|
+
final Object? error;
|
|
493
|
+
@override
|
|
494
|
+
final int? count;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
class _MockRpcResponse implements SupabaseRpcResponse {
|
|
498
|
+
_MockRpcResponse({this.data, this.error});
|
|
499
|
+
@override
|
|
500
|
+
final Object? data;
|
|
501
|
+
@override
|
|
502
|
+
final Object? error;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/// Build an in-memory mock Supabase client backed by [store] and [bucket].
|
|
506
|
+
SupabaseClientLike createMockSupabaseClient({
|
|
507
|
+
required Store store,
|
|
508
|
+
required FakeStorageBucket bucket,
|
|
509
|
+
}) {
|
|
510
|
+
final storageClient = _MockStorageClient(bucket);
|
|
511
|
+
|
|
512
|
+
Platform platformFrom(String value) =>
|
|
513
|
+
value == 'ios' ? Platform.ios : Platform.android;
|
|
514
|
+
|
|
515
|
+
Bundle rowToBundle(Map<String, dynamic> row) => mapRowToBundle(
|
|
516
|
+
SupabaseBundleRow.fromJson(row),
|
|
517
|
+
);
|
|
518
|
+
|
|
519
|
+
return _MockClient(store, storageClient, platformFrom, rowToBundle);
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
class _MockClient implements SupabaseClientLike {
|
|
523
|
+
_MockClient(
|
|
524
|
+
this._store,
|
|
525
|
+
this._storage,
|
|
526
|
+
this._platformFrom,
|
|
527
|
+
this._rowToBundle,
|
|
528
|
+
);
|
|
529
|
+
|
|
530
|
+
final Store _store;
|
|
531
|
+
final SupabaseStorageClientLike _storage;
|
|
532
|
+
final Platform Function(String) _platformFrom;
|
|
533
|
+
final Bundle Function(Map<String, dynamic>) _rowToBundle;
|
|
534
|
+
|
|
535
|
+
@override
|
|
536
|
+
SupabaseQueryBuilderLike from(String table) =>
|
|
537
|
+
_MockQueryBuilder(_store, table);
|
|
538
|
+
|
|
539
|
+
@override
|
|
540
|
+
SupabaseStorageClientLike get storage => _storage;
|
|
541
|
+
|
|
542
|
+
@override
|
|
543
|
+
Future<SupabaseRpcResponse> rpc(
|
|
544
|
+
String functionName, {
|
|
545
|
+
Map<String, dynamic>? params,
|
|
546
|
+
}) async {
|
|
547
|
+
if (functionName == 'get_channels') {
|
|
548
|
+
final channels = _store.bundleRows.values
|
|
549
|
+
.map((r) => r['channel'] as String)
|
|
550
|
+
.toSet()
|
|
551
|
+
.map((c) => {'channel': c})
|
|
552
|
+
.toList();
|
|
553
|
+
return _MockRpcResponse(data: channels, error: null);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
if (functionName == 'get_target_app_version_list') {
|
|
557
|
+
final platform = params!['app_platform'] as String;
|
|
558
|
+
final minBundleId = params['min_bundle_id'] as String;
|
|
559
|
+
final list = _store.bundleRows.values
|
|
560
|
+
.where((r) =>
|
|
561
|
+
r['platform'] == platform &&
|
|
562
|
+
(r['id'] as String).compareTo(minBundleId) >= 0 &&
|
|
563
|
+
r['target_app_version'] != null)
|
|
564
|
+
.map((r) => r['target_app_version'] as String)
|
|
565
|
+
.toSet()
|
|
566
|
+
.map((v) => {'target_app_version': v})
|
|
567
|
+
.toList();
|
|
568
|
+
return _MockRpcResponse(data: list, error: null);
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
if (functionName == 'get_update_info_by_app_version') {
|
|
572
|
+
final platform = params!['app_platform'] as String;
|
|
573
|
+
final appVersion = params['app_version'] as String;
|
|
574
|
+
final bundleId = params['bundle_id'] as String;
|
|
575
|
+
final minBundleId = params['min_bundle_id'] as String;
|
|
576
|
+
final channel = params['target_channel'] as String;
|
|
577
|
+
final targetAppVersionList =
|
|
578
|
+
List<String>.from(params['target_app_version_list'] as List? ?? []);
|
|
579
|
+
final cohort = params['cohort'] as String?;
|
|
580
|
+
|
|
581
|
+
final bundles = _store.bundleRows.values
|
|
582
|
+
.where((r) =>
|
|
583
|
+
r['enabled'] == true &&
|
|
584
|
+
r['platform'] == platform &&
|
|
585
|
+
r['channel'] == channel &&
|
|
586
|
+
(r['id'] as String).compareTo(minBundleId) >= 0 &&
|
|
587
|
+
(targetAppVersionList.contains(r['target_app_version'])))
|
|
588
|
+
.map(_rowToBundle)
|
|
589
|
+
.toList();
|
|
590
|
+
|
|
591
|
+
final info = await getUpdateInfo(
|
|
592
|
+
bundles,
|
|
593
|
+
AppVersionGetBundlesArgs(
|
|
594
|
+
appVersion: appVersion,
|
|
595
|
+
bundleId: bundleId,
|
|
596
|
+
channel: channel,
|
|
597
|
+
minBundleId: minBundleId,
|
|
598
|
+
platform: _platformFrom(platform),
|
|
599
|
+
cohort: cohort,
|
|
600
|
+
),
|
|
601
|
+
);
|
|
602
|
+
return _MockRpcResponse(
|
|
603
|
+
data: info == null ? [] : [_toUpdateInfoRow(info)],
|
|
604
|
+
error: null,
|
|
605
|
+
);
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
if (functionName == 'get_update_info_by_fingerprint_hash') {
|
|
609
|
+
final platform = params!['app_platform'] as String;
|
|
610
|
+
final bundleId = params['bundle_id'] as String;
|
|
611
|
+
final minBundleId = params['min_bundle_id'] as String;
|
|
612
|
+
final channel = params['target_channel'] as String;
|
|
613
|
+
final fingerprintHash = params['target_fingerprint_hash'] as String;
|
|
614
|
+
final cohort = params['cohort'] as String?;
|
|
615
|
+
|
|
616
|
+
final bundles = _store.bundleRows.values
|
|
617
|
+
.where((r) =>
|
|
618
|
+
r['enabled'] == true &&
|
|
619
|
+
r['platform'] == platform &&
|
|
620
|
+
r['channel'] == channel &&
|
|
621
|
+
(r['id'] as String).compareTo(minBundleId) >= 0 &&
|
|
622
|
+
r['fingerprint_hash'] == fingerprintHash)
|
|
623
|
+
.map(_rowToBundle)
|
|
624
|
+
.toList();
|
|
625
|
+
|
|
626
|
+
final info = await getUpdateInfo(
|
|
627
|
+
bundles,
|
|
628
|
+
FingerprintGetBundlesArgs(
|
|
629
|
+
fingerprintHash: fingerprintHash,
|
|
630
|
+
bundleId: bundleId,
|
|
631
|
+
channel: channel,
|
|
632
|
+
minBundleId: minBundleId,
|
|
633
|
+
platform: _platformFrom(platform),
|
|
634
|
+
cohort: cohort,
|
|
635
|
+
),
|
|
636
|
+
);
|
|
637
|
+
return _MockRpcResponse(
|
|
638
|
+
data: info == null ? [] : [_toUpdateInfoRow(info)],
|
|
639
|
+
error: null,
|
|
640
|
+
);
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
return _MockRpcResponse(data: null, error: 'Unsupported RPC: $functionName');
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
Map<String, dynamic> _toUpdateInfoRow(UpdateInfo info) => {
|
|
648
|
+
'id': info.id,
|
|
649
|
+
'should_force_update': info.shouldForceUpdate,
|
|
650
|
+
'message': info.message,
|
|
651
|
+
'status': info.status == UpdateStatus.rollback ? 'ROLLBACK' : 'UPDATE',
|
|
652
|
+
'storage_uri': info.storageUri,
|
|
653
|
+
'file_hash': info.fileHash,
|
|
654
|
+
};
|