@_nazmiforreal/flutter-ota 0.1.11 → 0.1.13
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/README.md +1 -1
- package/bin/flutter-ota-linux-x64 +0 -0
- package/dart-src/packages/cli-tools/.dart_tool/package_config.json +46 -76
- package/dart-src/packages/cli-tools/.dart_tool/package_graph.json +140 -166
- package/dart-src/packages/cli-tools/README.md +1 -1
- package/dart-src/packages/cli-tools/lib/flutter_ota_kit_cli.dart +30 -17
- package/dart-src/packages/cli-tools/lib/src/backend.dart +30 -31
- package/dart-src/packages/cli-tools/lib/src/cli_base.dart +3 -1
- package/dart-src/packages/cli-tools/lib/src/commands/build.dart +44 -43
- package/dart-src/packages/cli-tools/lib/src/commands/bundle.dart +265 -239
- package/dart-src/packages/cli-tools/lib/src/commands/channel.dart +75 -66
- package/dart-src/packages/cli-tools/lib/src/commands/config_command.dart +39 -35
- package/dart-src/packages/cli-tools/lib/src/commands/console.dart +30 -27
- package/dart-src/packages/cli-tools/lib/src/commands/deploy.dart +88 -72
- package/dart-src/packages/cli-tools/lib/src/commands/doctor.dart +66 -29
- package/dart-src/packages/cli-tools/lib/src/commands/fingerprint.dart +10 -6
- package/dart-src/packages/cli-tools/lib/src/commands/init.dart +299 -190
- package/dart-src/packages/cli-tools/lib/src/commands/keys.dart +24 -23
- package/dart-src/packages/cli-tools/lib/src/commands/migrate.dart +111 -97
- package/dart-src/packages/cli-tools/lib/src/commands/pocketbase.dart +237 -0
- package/dart-src/packages/cli-tools/lib/src/commands/rollback.dart +43 -34
- package/dart-src/packages/cli-tools/lib/src/commands/storage.dart +57 -53
- package/dart-src/packages/cli-tools/lib/src/config.dart +227 -93
- package/dart-src/packages/cli-tools/lib/src/operations.dart +9 -8
- package/dart-src/packages/cli-tools/lib/src/pack.dart +26 -25
- package/dart-src/packages/cli-tools/lib/src/pocketbase/data_bootstrap.dart +39 -0
- package/dart-src/packages/cli-tools/lib/src/pocketbase/hooks/bundles.pb.js +56 -0
- package/dart-src/packages/cli-tools/lib/src/pocketbase/installer.dart +189 -0
- package/dart-src/packages/cli-tools/lib/src/pocketbase/process_manager.dart +123 -0
- package/dart-src/packages/cli-tools/lib/src/pocketbase/schema_installer.dart +237 -0
- package/dart-src/packages/cli-tools/lib/src/runner.dart +36 -15
- package/dart-src/packages/cli-tools/lib/src/sign.dart +13 -5
- package/dart-src/packages/cli-tools/lib/src/ui/ui.dart +10 -13
- package/dart-src/packages/cli-tools/lib/src/util.dart +3 -1
- package/dart-src/packages/cli-tools/pubspec.lock +69 -116
- package/dart-src/packages/cli-tools/pubspec.yaml +25 -7
- package/dart-src/packages/cli-tools/test/backends_test.dart +62 -32
- package/dart-src/packages/cli-tools/test/build_pack_test.dart +4 -4
- package/dart-src/packages/cli-tools/test/cli_test.dart +89 -42
- package/dart-src/packages/cli-tools/test/mocks/mock_aws_s3_client.dart +8 -13
- package/dart-src/packages/cli-tools/test/mocks/mock_d1_client.dart +23 -18
- package/dart-src/packages/cli-tools/test/mocks/mock_pocketbase_client.dart +197 -0
- package/dart-src/packages/cli-tools/test/mocks/mock_postgres_client.dart +70 -46
- package/dart-src/packages/cli-tools/test/pocketbase_installer_test.dart +72 -0
- package/dart-src/packages/cli-tools/test/pocketbase_schema_test.dart +107 -0
- package/dart-src/packages/cli-tools/test/pocketbase_smoke_test.dart +59 -0
- package/dart-src/packages/core/README.md +2 -2
- package/dart-src/packages/core/example/lib/main.dart +69 -0
- package/dart-src/packages/core/lib/flutter_ota_kit_core.dart +1 -1
- package/dart-src/packages/core/lib/src/app_update_info.dart +17 -15
- package/dart-src/packages/core/lib/src/bundle.dart +102 -94
- package/dart-src/packages/core/lib/src/bundle_artifacts.dart +1 -2
- package/dart-src/packages/core/lib/src/bundle_patch_artifact.dart +15 -16
- package/dart-src/packages/core/lib/src/changed_asset.dart +24 -22
- package/dart-src/packages/core/lib/src/get_bundles_args.dart +13 -13
- package/dart-src/packages/core/lib/src/metadata.dart +9 -7
- package/dart-src/packages/core/lib/src/patch_info.dart +8 -7
- package/dart-src/packages/core/lib/src/platform.dart +4 -3
- package/dart-src/packages/core/lib/src/rollout.dart +14 -11
- package/dart-src/packages/core/lib/src/semver_range.dart +49 -39
- package/dart-src/packages/core/lib/src/semver_version.dart +8 -7
- package/dart-src/packages/core/lib/src/status.dart +4 -3
- package/dart-src/packages/core/lib/src/update_bundle_params.dart +7 -7
- package/dart-src/packages/core/lib/src/uuid.dart +1 -2
- package/dart-src/packages/core/pubspec.yaml +6 -7
- package/dart-src/packages/core/test/rollout_test.dart +15 -8
- package/dart-src/packages/core/test/semver_test.dart +5 -2
- package/dart-src/packages/core/test/types_test.dart +26 -24
- package/dart-src/packages/core/test/uuid_test.dart +3 -1
- package/dart-src/packages/core/tool/dbg.dart +1 -0
- package/dart-src/plugins/aws/CHANGELOG.md +6 -0
- package/dart-src/plugins/aws/example/lib/main.dart +60 -0
- package/dart-src/plugins/aws/lib/flutter_ota_kit_aws.dart +5 -1
- package/dart-src/plugins/aws/lib/src/aws_cloudfront_client.dart +11 -16
- package/dart-src/plugins/aws/lib/src/aws_cloudfront_signer.dart +7 -2
- package/dart-src/plugins/aws/lib/src/aws_database.dart +14 -18
- package/dart-src/plugins/aws/lib/src/aws_s3_client.dart +23 -18
- package/dart-src/plugins/aws/lib/src/aws_ssm_client.dart +5 -4
- package/dart-src/plugins/aws/lib/src/aws_storage.dart +2 -3
- package/dart-src/plugins/aws/lib/src/aws_storage_profile.dart +10 -16
- package/dart-src/plugins/aws/lib/src/with_cloudfront_signed_url.dart +6 -9
- package/dart-src/plugins/aws/pubspec.yaml +15 -10
- package/dart-src/plugins/aws/test/aws_database_test.dart +51 -57
- package/dart-src/plugins/aws/test/mock_aws_s3_client.dart +8 -13
- package/dart-src/plugins/cloudflare/CHANGELOG.md +7 -0
- package/dart-src/plugins/cloudflare/example/lib/main.dart +62 -0
- package/dart-src/plugins/cloudflare/lib/flutter_ota_kit_cloudflare.dart +4 -2
- package/dart-src/plugins/cloudflare/lib/src/cloudflare_worker_database.dart +3 -3
- package/dart-src/plugins/cloudflare/lib/src/d1_build_where.dart +3 -2
- package/dart-src/plugins/cloudflare/lib/src/d1_bundle_mapper.dart +24 -27
- package/dart-src/plugins/cloudflare/lib/src/d1_database_plugin.dart +22 -30
- package/dart-src/plugins/cloudflare/lib/src/r2_config.dart +1 -1
- package/dart-src/plugins/cloudflare/lib/src/r2_s3_client.dart +21 -29
- package/dart-src/plugins/cloudflare/lib/src/r2_storage.dart +7 -7
- package/dart-src/plugins/cloudflare/lib/src/r2_storage_profile.dart +7 -15
- package/dart-src/plugins/cloudflare/pubspec.yaml +14 -9
- package/dart-src/plugins/cloudflare/test/cloudflare_worker_database_test.dart +46 -42
- package/dart-src/plugins/cloudflare/test/d1_database_test.dart +100 -90
- package/dart-src/plugins/cloudflare/test/mock_d1_client.dart +23 -18
- package/dart-src/plugins/cloudflare/test/mock_r2_client.dart +8 -13
- package/dart-src/plugins/cloudflare/test/r2_storage_test.dart +1 -4
- package/dart-src/plugins/plugin-core/CHANGELOG.md +16 -0
- package/dart-src/plugins/plugin-core/example/lib/main.dart +61 -0
- package/dart-src/plugins/plugin-core/lib/flutter_ota_kit_plugin_core.dart +3 -7
- package/dart-src/plugins/plugin-core/lib/src/asset_storage_layout.dart +5 -5
- package/dart-src/plugins/plugin-core/lib/src/bundle_helpers.dart +23 -0
- package/dart-src/plugins/plugin-core/lib/src/bundle_storage_layout.dart +16 -10
- package/dart-src/plugins/plugin-core/lib/src/bundle_unit_of_work.dart +31 -26
- package/dart-src/plugins/plugin-core/lib/src/calculate_pagination.dart +5 -1
- package/dart-src/plugins/plugin-core/lib/src/content_addressed_assets.dart +1 -1
- package/dart-src/plugins/plugin-core/lib/src/create_blob_database_plugin.dart +78 -71
- package/dart-src/plugins/plugin-core/lib/src/create_database_plugin.dart +56 -35
- package/dart-src/plugins/plugin-core/lib/src/create_storage_plugin.dart +10 -13
- package/dart-src/plugins/plugin-core/lib/src/paginate_bundles.dart +30 -17
- package/dart-src/plugins/plugin-core/lib/src/parse_storage_uri.dart +3 -1
- package/dart-src/plugins/plugin-core/lib/src/query_bundles.dart +5 -3
- package/dart-src/plugins/plugin-core/lib/src/request_update_bundle_state.dart +2 -4
- package/dart-src/plugins/plugin-core/lib/src/storage_helpers.dart +18 -0
- package/dart-src/plugins/plugin-core/lib/src/types.dart +7 -6
- package/dart-src/plugins/plugin-core/pubspec.yaml +12 -9
- package/dart-src/plugins/plugin-core/test/asset_storage_layout_test.dart +13 -9
- package/dart-src/plugins/plugin-core/test/bundle_unit_of_work_test.dart +28 -15
- package/dart-src/plugins/plugin-core/test/create_blob_database_plugin_test.dart +254 -122
- package/dart-src/plugins/plugin-core/test/create_database_plugin_test.dart +36 -32
- package/dart-src/plugins/plugin-core/test/create_storage_plugin_test.dart +18 -15
- package/dart-src/plugins/plugin-core/test/generate_min_bundle_id_test.dart +8 -3
- package/dart-src/plugins/plugin-core/test/get_update_info_test.dart +682 -693
- package/dart-src/plugins/plugin-core/test/helpers_test.dart +45 -21
- package/dart-src/plugins/plugin-core/test/query_bundles_test.dart +76 -13
- package/dart-src/plugins/plugin-core/test/uuidv7_test.dart +20 -9
- package/dart-src/plugins/pocketbase/CHANGELOG.md +8 -0
- package/dart-src/plugins/pocketbase/LICENSE +21 -0
- package/dart-src/plugins/pocketbase/README.md +54 -0
- package/dart-src/plugins/pocketbase/example/lib/main.dart +62 -0
- package/dart-src/plugins/pocketbase/lib/flutter_ota_kit_pocketbase.dart +28 -0
- package/dart-src/plugins/pocketbase/lib/src/pocketbase_bundle_mapper.dart +121 -0
- package/dart-src/plugins/pocketbase/lib/src/pocketbase_client.dart +349 -0
- package/dart-src/plugins/pocketbase/lib/src/pocketbase_config.dart +61 -0
- package/dart-src/plugins/pocketbase/lib/src/pocketbase_database.dart +278 -0
- package/dart-src/plugins/pocketbase/lib/src/pocketbase_storage.dart +255 -0
- package/dart-src/plugins/pocketbase/pubspec.yaml +26 -0
- package/dart-src/plugins/pocketbase/test/pb_uri_test.dart +12 -0
- package/dart-src/plugins/postgres/CHANGELOG.md +6 -0
- package/dart-src/plugins/postgres/example/lib/main.dart +67 -0
- package/dart-src/plugins/postgres/lib/flutter_ota_kit_postgres.dart +3 -6
- package/dart-src/plugins/postgres/lib/src/postgres_bundle_mapper.dart +32 -52
- package/dart-src/plugins/postgres/lib/src/postgres_config.dart +3 -1
- package/dart-src/plugins/postgres/lib/src/postgres_database.dart +28 -38
- package/dart-src/plugins/postgres/lib/src/postgres_get_update_info.dart +16 -14
- package/dart-src/plugins/postgres/lib/src/postgres_storage.dart +26 -14
- package/dart-src/plugins/postgres/lib/src/postgres_types.dart +27 -28
- package/dart-src/plugins/postgres/pubspec.yaml +14 -9
- package/dart-src/plugins/postgres/test/mock_postgres_client.dart +62 -39
- package/dart-src/plugins/postgres/test/postgres_bundle_mapper_test.dart +3 -4
- package/dart-src/plugins/postgres/test/postgres_database_test.dart +102 -73
- package/dart-src/plugins/postgres/test/postgres_storage_runtime_test.dart +60 -0
- package/dart-src/plugins/supabase/CHANGELOG.md +17 -0
- package/dart-src/plugins/supabase/README.md +2 -2
- package/dart-src/plugins/supabase/example/lib/main.dart +63 -0
- package/dart-src/plugins/supabase/lib/flutter_ota_kit_supabase.dart +2 -0
- package/dart-src/plugins/supabase/lib/src/error_message.dart +11 -0
- package/dart-src/plugins/supabase/lib/src/supabase_bundle_mapper.dart +34 -58
- package/dart-src/plugins/supabase/lib/src/supabase_client_adapter.dart +3 -13
- package/dart-src/plugins/supabase/lib/src/supabase_client_http.dart +34 -52
- package/dart-src/plugins/supabase/lib/src/supabase_database.dart +38 -30
- package/dart-src/plugins/supabase/lib/src/supabase_edge_function_storage.dart +21 -29
- package/dart-src/plugins/supabase/lib/src/supabase_signed_url_batcher.dart +7 -19
- package/dart-src/plugins/supabase/lib/src/supabase_storage.dart +18 -29
- package/dart-src/plugins/supabase/lib/src/types.dart +26 -26
- package/dart-src/plugins/supabase/pubspec.yaml +14 -9
- package/dart-src/plugins/supabase/test/mock_supabase_client.dart +57 -57
- package/dart-src/plugins/supabase/test/supabase_bundle_mapper_test.dart +30 -27
- package/dart-src/plugins/supabase/test/supabase_database_test.dart +26 -35
- package/dart-src/plugins/supabase/test/supabase_storage_test.dart +27 -34
- package/package.json +2 -2
- package/scripts/vendor.sh +1 -1
|
@@ -118,8 +118,7 @@ class MockBuilder
|
|
|
118
118
|
bool _ascending = false;
|
|
119
119
|
|
|
120
120
|
@override
|
|
121
|
-
SupabaseFilterBuilderLike select(String columns,
|
|
122
|
-
{bool? count, bool? head}) =>
|
|
121
|
+
SupabaseFilterBuilderLike select(String columns, {bool? count, bool? head}) =>
|
|
123
122
|
_asSelect(count: count, head: head);
|
|
124
123
|
|
|
125
124
|
MockBuilder _asSelect({bool? count, bool? head}) {
|
|
@@ -175,14 +174,18 @@ class MockBuilder
|
|
|
175
174
|
@override
|
|
176
175
|
SupabaseFilterBuilderLike isFilter(String column, Object? value) => this;
|
|
177
176
|
@override
|
|
178
|
-
SupabaseFilterBuilderLike not(
|
|
179
|
-
|
|
177
|
+
SupabaseFilterBuilderLike not(
|
|
178
|
+
String column,
|
|
179
|
+
String operator,
|
|
180
|
+
Object? value,
|
|
181
|
+
) => this;
|
|
180
182
|
@override
|
|
181
183
|
SupabaseFilterBuilderLike order(String column, {bool? ascending}) {
|
|
182
184
|
_order = column;
|
|
183
185
|
_ascending = ascending ?? false;
|
|
184
186
|
return this;
|
|
185
187
|
}
|
|
188
|
+
|
|
186
189
|
@override
|
|
187
190
|
SupabaseFilterBuilderLike limit(int value) => this;
|
|
188
191
|
@override
|
|
@@ -207,8 +210,9 @@ class MockBuilder
|
|
|
207
210
|
}
|
|
208
211
|
// select
|
|
209
212
|
if (_head && _count) {
|
|
210
|
-
final total =
|
|
211
|
-
|
|
213
|
+
final total = _table == 'bundles'
|
|
214
|
+
? _client._bundles.length
|
|
215
|
+
: _client._patches.length;
|
|
212
216
|
return MockResponse(data: const [], count: total, error: null);
|
|
213
217
|
}
|
|
214
218
|
if (_table == 'bundle_patches') {
|
|
@@ -225,8 +229,11 @@ class MockBuilder
|
|
|
225
229
|
rows = rows.where((r) => r['id'] == _eq['id']).toList();
|
|
226
230
|
}
|
|
227
231
|
if (_table == 'bundles' && _order == 'id') {
|
|
228
|
-
rows.sort(
|
|
229
|
-
|
|
232
|
+
rows.sort(
|
|
233
|
+
(a, b) =>
|
|
234
|
+
(_ascending ? 1 : -1) *
|
|
235
|
+
(a['id'] as String).compareTo(b['id'] as String),
|
|
236
|
+
);
|
|
230
237
|
}
|
|
231
238
|
if (_single) {
|
|
232
239
|
return MockResponse(data: rows.isEmpty ? null : rows.first, error: null);
|
|
@@ -241,22 +248,34 @@ class MockBucket implements SupabaseStorageBucketLike {
|
|
|
241
248
|
final String _bucket;
|
|
242
249
|
|
|
243
250
|
@override
|
|
244
|
-
Future<SupabaseSignedUrlResult> createSignedUrl(
|
|
245
|
-
|
|
251
|
+
Future<SupabaseSignedUrlResult> createSignedUrl(
|
|
252
|
+
String path,
|
|
253
|
+
int expiresIn,
|
|
254
|
+
) async => MockSignedUrlResult(
|
|
255
|
+
signedUrl: 'https://signed/$_bucket/$path',
|
|
256
|
+
error: null,
|
|
257
|
+
);
|
|
246
258
|
|
|
247
259
|
@override
|
|
248
260
|
Future<SupabaseSignedUrlListResult> createSignedUrls(
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
261
|
+
List<String> paths,
|
|
262
|
+
int expiresIn,
|
|
263
|
+
) async => MockSignedUrlListResult(
|
|
264
|
+
data: paths
|
|
265
|
+
.map(
|
|
266
|
+
(p) => MockSignedUrlResult(signedUrl: 'https://signed/$_bucket/$p'),
|
|
267
|
+
)
|
|
268
|
+
.toList(),
|
|
269
|
+
error: null,
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
@override
|
|
273
|
+
Future<SupabaseUploadResult> upload(
|
|
274
|
+
String path,
|
|
275
|
+
List<int> fileBytes, {
|
|
276
|
+
String? contentType,
|
|
277
|
+
String? cacheControl,
|
|
278
|
+
}) async {
|
|
260
279
|
_client._storage[path] = Uint8List.fromList(fileBytes);
|
|
261
280
|
return MockUploadResult(data: {'Key': path}, error: null);
|
|
262
281
|
}
|
|
@@ -291,8 +310,7 @@ class MockStorageClient implements SupabaseStorageClientLike {
|
|
|
291
310
|
MockStorageClient(this._client);
|
|
292
311
|
final MockSupabaseClient _client;
|
|
293
312
|
@override
|
|
294
|
-
SupabaseStorageBucketLike from(String bucket) =>
|
|
295
|
-
MockBucket(_client, bucket);
|
|
313
|
+
SupabaseStorageBucketLike from(String bucket) => MockBucket(_client, bucket);
|
|
296
314
|
}
|
|
297
315
|
|
|
298
316
|
class MockSupabaseClient implements SupabaseClientLike {
|
|
@@ -307,8 +325,10 @@ class MockSupabaseClient implements SupabaseClientLike {
|
|
|
307
325
|
SupabaseStorageClientLike get storage => MockStorageClient(this);
|
|
308
326
|
|
|
309
327
|
@override
|
|
310
|
-
Future<SupabaseRpcResponse> rpc(
|
|
311
|
-
|
|
328
|
+
Future<SupabaseRpcResponse> rpc(
|
|
329
|
+
String functionName, {
|
|
330
|
+
Map<String, dynamic>? params,
|
|
331
|
+
}) async {
|
|
312
332
|
if (functionName == 'get_channels') {
|
|
313
333
|
return const MockRpcResponse(
|
|
314
334
|
data: [
|
|
@@ -327,16 +347,16 @@ class MockSupabaseClient implements SupabaseClientLike {
|
|
|
327
347
|
// ---------------------------------------------------------------------------
|
|
328
348
|
|
|
329
349
|
FlutterPatcherConfig _config() => FlutterPatcherConfig(
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
350
|
+
provider: 'supabase',
|
|
351
|
+
supabase: SupabaseConfigJson(
|
|
352
|
+
url: 'https://demo.supabase.co',
|
|
353
|
+
serviceRoleKey: 'service-role-key',
|
|
354
|
+
bucket: 'bundles',
|
|
355
|
+
),
|
|
356
|
+
channel: 'production',
|
|
357
|
+
platform: 'android',
|
|
358
|
+
source: './dist',
|
|
359
|
+
);
|
|
340
360
|
|
|
341
361
|
Backend _backend(MockSupabaseClient mock) =>
|
|
342
362
|
resolveBackend(_config(), supabaseClientFactory: (url, key) => mock);
|
|
@@ -357,7 +377,11 @@ void main() {
|
|
|
357
377
|
await File(p.join(dir.path, 'main.dart')).writeAsString('print(1);');
|
|
358
378
|
final bundle = await deployBundle(
|
|
359
379
|
backend,
|
|
360
|
-
|
|
380
|
+
DeployOptions(
|
|
381
|
+
source: dir.path,
|
|
382
|
+
channel: 'production',
|
|
383
|
+
platform: 'android',
|
|
384
|
+
),
|
|
361
385
|
);
|
|
362
386
|
expect(bundle.enabled, isTrue);
|
|
363
387
|
expect(mock._bundles.containsKey(bundle.id), isTrue);
|
|
@@ -374,9 +398,16 @@ void main() {
|
|
|
374
398
|
await File(p.join(dir.path, 'a.txt')).writeAsString('x');
|
|
375
399
|
final deployed = await deployBundle(
|
|
376
400
|
backend,
|
|
377
|
-
|
|
401
|
+
DeployOptions(
|
|
402
|
+
source: dir.path,
|
|
403
|
+
channel: 'production',
|
|
404
|
+
platform: 'android',
|
|
405
|
+
),
|
|
406
|
+
);
|
|
407
|
+
final res = await listBundles(
|
|
408
|
+
backend,
|
|
409
|
+
ListOptions(channel: 'production'),
|
|
378
410
|
);
|
|
379
|
-
final res = await listBundles(backend, ListOptions(channel: 'production'));
|
|
380
411
|
expect(res.data.map((b) => b.id), contains(deployed.id));
|
|
381
412
|
} finally {
|
|
382
413
|
await dir.delete(recursive: true);
|
|
@@ -394,7 +425,11 @@ void main() {
|
|
|
394
425
|
await File(p.join(dir.path, 'a.txt')).writeAsString('x');
|
|
395
426
|
final deployed = await deployBundle(
|
|
396
427
|
backend,
|
|
397
|
-
|
|
428
|
+
DeployOptions(
|
|
429
|
+
source: dir.path,
|
|
430
|
+
channel: 'production',
|
|
431
|
+
platform: 'android',
|
|
432
|
+
),
|
|
398
433
|
);
|
|
399
434
|
await deleteBundle(backend, deployed.id);
|
|
400
435
|
expect(mock._bundles.containsKey(deployed.id), isFalse);
|
|
@@ -409,11 +444,19 @@ void main() {
|
|
|
409
444
|
await File(p.join(dir.path, 'a.txt')).writeAsString('x');
|
|
410
445
|
final first = await deployBundle(
|
|
411
446
|
backend,
|
|
412
|
-
|
|
447
|
+
DeployOptions(
|
|
448
|
+
source: dir.path,
|
|
449
|
+
channel: 'production',
|
|
450
|
+
platform: 'android',
|
|
451
|
+
),
|
|
413
452
|
);
|
|
414
453
|
final second = await deployBundle(
|
|
415
454
|
backend,
|
|
416
|
-
|
|
455
|
+
DeployOptions(
|
|
456
|
+
source: dir.path,
|
|
457
|
+
channel: 'production',
|
|
458
|
+
platform: 'android',
|
|
459
|
+
),
|
|
417
460
|
);
|
|
418
461
|
final disabled = await rollbackChannel(backend, 'production');
|
|
419
462
|
expect(disabled, equals(second.id));
|
|
@@ -430,7 +473,11 @@ void main() {
|
|
|
430
473
|
await File(p.join(dir.path, 'a.txt')).writeAsString('x');
|
|
431
474
|
final b = await deployBundle(
|
|
432
475
|
backend,
|
|
433
|
-
|
|
476
|
+
DeployOptions(
|
|
477
|
+
source: dir.path,
|
|
478
|
+
channel: 'production',
|
|
479
|
+
platform: 'android',
|
|
480
|
+
),
|
|
434
481
|
);
|
|
435
482
|
await promoteBundle(backend, b.id, 'staging');
|
|
436
483
|
expect(mock._bundles[b.id]!['channel'], equals('staging'));
|
|
@@ -29,8 +29,7 @@ class MockAwsS3Client implements AwsS3ClientLike {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
@override
|
|
32
|
-
Future<bool> headObject(String key) async =>
|
|
33
|
-
store.objects.containsKey(key);
|
|
32
|
+
Future<bool> headObject(String key) async => store.objects.containsKey(key);
|
|
34
33
|
|
|
35
34
|
@override
|
|
36
35
|
Future<List<int>> getObjectAsBytes(String key) async {
|
|
@@ -64,20 +63,16 @@ class MockAwsS3Client implements AwsS3ClientLike {
|
|
|
64
63
|
}
|
|
65
64
|
|
|
66
65
|
@override
|
|
67
|
-
Future<void> putObject(
|
|
68
|
-
String key,
|
|
69
|
-
List<int> body,
|
|
70
|
-
String contentType,
|
|
71
|
-
) async {
|
|
66
|
+
Future<void> putObject(String key, List<int> body, String contentType) async {
|
|
72
67
|
store.objects[key] = body;
|
|
73
68
|
}
|
|
74
69
|
}
|
|
75
70
|
|
|
76
71
|
/// Build an [AwsS3StorageConfig] wired to an in-memory [Store].
|
|
77
72
|
AwsS3StorageConfig mockConfig(Store store) => AwsS3StorageConfig(
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
73
|
+
bucketName: 'test-bucket',
|
|
74
|
+
region: 'us-east-1',
|
|
75
|
+
accessKeyId: 'test-access-key',
|
|
76
|
+
secretAccessKey: 'test-secret',
|
|
77
|
+
clientFactory: (_) => MockAwsS3Client(store),
|
|
78
|
+
);
|
|
@@ -76,7 +76,9 @@ class MockD1Client implements D1ClientLike {
|
|
|
76
76
|
if (where.contains('target_app_version = ?')) {
|
|
77
77
|
if (row['target_app_version'] != params[p++]) return false;
|
|
78
78
|
}
|
|
79
|
-
if (where.contains(
|
|
79
|
+
if (where.contains(
|
|
80
|
+
'target_app_version IN (SELECT value FROM json_each(?))',
|
|
81
|
+
)) {
|
|
80
82
|
if (!_asList(params[p++]).contains(row['target_app_version'])) {
|
|
81
83
|
return false;
|
|
82
84
|
}
|
|
@@ -140,7 +142,9 @@ class MockD1Client implements D1ClientLike {
|
|
|
140
142
|
final channels = {
|
|
141
143
|
for (final row in store.bundleRows) row['channel'] as String,
|
|
142
144
|
};
|
|
143
|
-
return [
|
|
145
|
+
return [
|
|
146
|
+
for (final c in channels) {'channel': c},
|
|
147
|
+
];
|
|
144
148
|
}
|
|
145
149
|
|
|
146
150
|
if (sql.contains('SELECT target_app_version FROM bundles')) {
|
|
@@ -164,9 +168,11 @@ class MockD1Client implements D1ClientLike {
|
|
|
164
168
|
.where((row) => ids.contains(row['bundle_id']))
|
|
165
169
|
.toList();
|
|
166
170
|
rows.sort(
|
|
167
|
-
(a, b) =>
|
|
168
|
-
(a['
|
|
169
|
-
|
|
171
|
+
(a, b) =>
|
|
172
|
+
(a['order_index'] as num).compareTo(b['order_index'] as num) +
|
|
173
|
+
(a['base_bundle_id'] as String).compareTo(
|
|
174
|
+
b['base_bundle_id'] as String,
|
|
175
|
+
),
|
|
170
176
|
);
|
|
171
177
|
return rows;
|
|
172
178
|
}
|
|
@@ -195,8 +201,7 @@ class MockD1Client implements D1ClientLike {
|
|
|
195
201
|
'rollout_cohort_count': params[15],
|
|
196
202
|
'target_cohorts': params[16],
|
|
197
203
|
};
|
|
198
|
-
store.bundleRows
|
|
199
|
-
.removeWhere((r) => r['id'] == row['id']);
|
|
204
|
+
store.bundleRows.removeWhere((r) => r['id'] == row['id']);
|
|
200
205
|
store.bundleRows.add(row);
|
|
201
206
|
return const [];
|
|
202
207
|
}
|
|
@@ -219,8 +224,9 @@ class MockD1Client implements D1ClientLike {
|
|
|
219
224
|
if (sql.contains('DELETE FROM bundles')) {
|
|
220
225
|
final id = params[0];
|
|
221
226
|
store.bundleRows.removeWhere((r) => r['id'] == id);
|
|
222
|
-
store.patchRows
|
|
223
|
-
|
|
227
|
+
store.patchRows.removeWhere(
|
|
228
|
+
(r) => r['bundle_id'] == id || r['base_bundle_id'] == id,
|
|
229
|
+
);
|
|
224
230
|
return const [];
|
|
225
231
|
}
|
|
226
232
|
|
|
@@ -240,17 +246,16 @@ class MockD1Client implements D1ClientLike {
|
|
|
240
246
|
|
|
241
247
|
/// Build a [D1DatabaseConfig] wired to an in-memory [Store].
|
|
242
248
|
D1DatabaseConfig mockConfig(Store store) => D1DatabaseConfig(
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
249
|
+
databaseId: 'test-db',
|
|
250
|
+
accountId: 'test-account',
|
|
251
|
+
cloudflareApiToken: 'test-token',
|
|
252
|
+
clientFactory: (_) => MockD1Client(store),
|
|
253
|
+
);
|
|
248
254
|
|
|
249
255
|
/// Construct the plugin with an in-memory store.
|
|
250
256
|
DatabasePlugin newPlugin(Store store) => d1Database(mockConfig(store))();
|
|
251
257
|
|
|
252
258
|
/// Construct the Workers D1 variant with an in-memory store.
|
|
253
|
-
DatabasePlugin newWorkerPlugin(Store store) =>
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
)();
|
|
259
|
+
DatabasePlugin newWorkerPlugin(Store store) => cloudflareWorkerDatabase(
|
|
260
|
+
CloudflareWorkerDatabaseConfig(getDb: () => MockD1Client(store)),
|
|
261
|
+
)();
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/// In-memory mock of [PocketBaseClient] for tests.
|
|
2
|
+
///
|
|
3
|
+
/// Implements the same surface as the real client but stores everything in
|
|
4
|
+
/// Dart maps. Used by the `pocketbase backend` lifecycle test in
|
|
5
|
+
/// `backends_test.dart`.
|
|
6
|
+
library;
|
|
7
|
+
|
|
8
|
+
import 'package:flutter_ota_kit_pocketbase/flutter_ota_kit_pocketbase.dart';
|
|
9
|
+
|
|
10
|
+
class MockPocketBaseClient implements PocketBaseClient {
|
|
11
|
+
MockPocketBaseClient(this.store);
|
|
12
|
+
|
|
13
|
+
final PocketBaseStore store;
|
|
14
|
+
|
|
15
|
+
bool _closed = false;
|
|
16
|
+
bool get isClosed => _closed;
|
|
17
|
+
String? _email;
|
|
18
|
+
String? _password;
|
|
19
|
+
|
|
20
|
+
@override
|
|
21
|
+
void adminCredentials(String email, String password) {
|
|
22
|
+
_email = email;
|
|
23
|
+
_password = password;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
@override
|
|
27
|
+
Future<PocketBaseClient> authenticate(String email, String password) async {
|
|
28
|
+
_email = email;
|
|
29
|
+
_password = password;
|
|
30
|
+
return this;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
void _checkAuth() {
|
|
34
|
+
if (_email == null || _password == null) {
|
|
35
|
+
throw const PocketBaseException('not authenticated');
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
@override
|
|
40
|
+
Future<bool> health() async => true;
|
|
41
|
+
|
|
42
|
+
@override
|
|
43
|
+
Future<bool> recordExists(String collection, String id) async {
|
|
44
|
+
_checkAuth();
|
|
45
|
+
return store.records(collection).containsKey(id);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
@override
|
|
49
|
+
Future<T?> getRecord<T>(
|
|
50
|
+
String collection,
|
|
51
|
+
String id,
|
|
52
|
+
T Function(Map<String, dynamic>) fromJson,
|
|
53
|
+
) async {
|
|
54
|
+
_checkAuth();
|
|
55
|
+
final r = store.records(collection)[id];
|
|
56
|
+
if (r == null) return null;
|
|
57
|
+
return fromJson(Map<String, dynamic>.from(r));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
@override
|
|
61
|
+
Future<PocketBaseList<T>> listRecords<T>(
|
|
62
|
+
String collection,
|
|
63
|
+
T Function(Map<String, dynamic>) fromJson, {
|
|
64
|
+
String? filter,
|
|
65
|
+
String? sort,
|
|
66
|
+
int page = 1,
|
|
67
|
+
int perPage = 50,
|
|
68
|
+
}) async {
|
|
69
|
+
_checkAuth();
|
|
70
|
+
// Sort the underlying JSON maps BEFORE decoding to typed objects, so the
|
|
71
|
+
// sort field is always available regardless of the decoded bean shape.
|
|
72
|
+
final rawList = store.records(collection).values.toList();
|
|
73
|
+
if (sort != null && sort.isNotEmpty) {
|
|
74
|
+
final desc = sort.startsWith('-');
|
|
75
|
+
final field = desc ? sort.substring(1) : sort;
|
|
76
|
+
rawList.sort((a, b) {
|
|
77
|
+
final av = a[field]?.toString() ?? '';
|
|
78
|
+
final bv = b[field]?.toString() ?? '';
|
|
79
|
+
final cmp = av.compareTo(bv);
|
|
80
|
+
return desc ? -cmp : cmp;
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
final all = rawList
|
|
84
|
+
.map((j) => fromJson(Map<String, dynamic>.from(j)))
|
|
85
|
+
.toList();
|
|
86
|
+
final start = (page - 1) * perPage;
|
|
87
|
+
final end = (start + perPage).clamp(0, all.length);
|
|
88
|
+
final items = start >= all.length ? <T>[] : all.sublist(start, end);
|
|
89
|
+
final totalPages = all.isEmpty ? 0 : (all.length / perPage).ceil();
|
|
90
|
+
return PocketBaseList<T>(
|
|
91
|
+
items: items,
|
|
92
|
+
page: page,
|
|
93
|
+
perPage: perPage,
|
|
94
|
+
totalItems: all.length,
|
|
95
|
+
totalPages: totalPages,
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
@override
|
|
100
|
+
Future<T> createRecord<T>(
|
|
101
|
+
String collection,
|
|
102
|
+
Map<String, dynamic> body,
|
|
103
|
+
T Function(Map<String, dynamic>) fromJson,
|
|
104
|
+
) async {
|
|
105
|
+
_checkAuth();
|
|
106
|
+
final id =
|
|
107
|
+
(body['id'] as String?) ??
|
|
108
|
+
DateTime.now().microsecondsSinceEpoch.toString();
|
|
109
|
+
final existing = store.records(collection)[id];
|
|
110
|
+
if (existing != null) {
|
|
111
|
+
// PB returns 409 on duplicate id; the mock simulates the real API by
|
|
112
|
+
// throwing. Callers should use updateRecord() to patch existing rows.
|
|
113
|
+
throw const PocketBaseException('mock: record id already exists');
|
|
114
|
+
}
|
|
115
|
+
store.records(collection)[id] = Map<String, dynamic>.from(body);
|
|
116
|
+
return fromJson(store.records(collection)[id]!);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
@override
|
|
120
|
+
Future<T> updateRecord<T>(
|
|
121
|
+
String collection,
|
|
122
|
+
String id,
|
|
123
|
+
Map<String, dynamic> body,
|
|
124
|
+
T Function(Map<String, dynamic>) fromJson,
|
|
125
|
+
) async {
|
|
126
|
+
_checkAuth();
|
|
127
|
+
final existing = store.records(collection);
|
|
128
|
+
final current = existing[id];
|
|
129
|
+
if (current == null) {
|
|
130
|
+
throw PocketBaseException('not_found: $id');
|
|
131
|
+
}
|
|
132
|
+
current.addAll(body);
|
|
133
|
+
return fromJson(Map<String, dynamic>.from(current));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
@override
|
|
137
|
+
Future<void> deleteRecord(String collection, String id) async {
|
|
138
|
+
_checkAuth();
|
|
139
|
+
store.records(collection).remove(id);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
@override
|
|
143
|
+
Future<T> uploadFile<T>(
|
|
144
|
+
String collection,
|
|
145
|
+
String recordId,
|
|
146
|
+
String fieldName,
|
|
147
|
+
String filename,
|
|
148
|
+
List<int> bytes, {
|
|
149
|
+
Map<String, String>? extraFields,
|
|
150
|
+
required T Function(Map<String, dynamic>) fromJson,
|
|
151
|
+
}) async {
|
|
152
|
+
_checkAuth();
|
|
153
|
+
final rec = store.records(collection)[recordId];
|
|
154
|
+
if (rec == null) throw PocketBaseException('not_found: $recordId');
|
|
155
|
+
rec[fieldName] = filename;
|
|
156
|
+
store.fileStorage['$recordId/$filename'] = List<int>.from(bytes);
|
|
157
|
+
return fromJson(Map<String, dynamic>.from(rec));
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
@override
|
|
161
|
+
Future<String> getFileToken(String recordId, String filename) async => 'mock';
|
|
162
|
+
|
|
163
|
+
@override
|
|
164
|
+
String fileUrl(
|
|
165
|
+
String collection,
|
|
166
|
+
String recordId,
|
|
167
|
+
String filename, [
|
|
168
|
+
String? token,
|
|
169
|
+
]) => 'pb://mock/$collection/$recordId/$filename';
|
|
170
|
+
|
|
171
|
+
@override
|
|
172
|
+
Future<List<int>> downloadFile(String url) async {
|
|
173
|
+
final match = RegExp(r'pb://mock/[^/]+/([^/]+)/(.+)$').firstMatch(url);
|
|
174
|
+
if (match == null) throw PocketBaseException('bad url: $url');
|
|
175
|
+
final key = '${match.group(1)}/${match.group(2)}';
|
|
176
|
+
final bytes = store.fileStorage[key];
|
|
177
|
+
if (bytes == null) throw PocketBaseException('not found: $key');
|
|
178
|
+
return bytes;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
@override
|
|
182
|
+
void close() {
|
|
183
|
+
_closed = true;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/// In-memory store backing [MockPocketBaseClient].
|
|
188
|
+
class PocketBaseStore {
|
|
189
|
+
final Map<String, Map<String, Map<String, dynamic>>> _records = {};
|
|
190
|
+
final Map<String, List<int>> _files = {};
|
|
191
|
+
|
|
192
|
+
Map<String, Map<String, dynamic>> records(String collection) =>
|
|
193
|
+
_records.putIfAbsent(collection, () => <String, Map<String, dynamic>>{});
|
|
194
|
+
|
|
195
|
+
Map<String, List<int>> get fileStorage => _files;
|
|
196
|
+
Map<String, Map<String, Map<String, dynamic>>> get allRecords => _records;
|
|
197
|
+
}
|