@_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,264 @@
|
|
|
1
|
+
import 'dart:convert' show jsonDecode, jsonEncode;
|
|
2
|
+
import 'dart:io' show Directory, File;
|
|
3
|
+
|
|
4
|
+
import 'package:flutter_patcher_core/flutter_patcher_core.dart'
|
|
5
|
+
show Bundle, FingerprintGetBundlesArgs, Platform;
|
|
6
|
+
import 'package:flutter_patcher_plugin_core/flutter_patcher_plugin_core.dart'
|
|
7
|
+
show DatabaseBundleQueryOptions;
|
|
8
|
+
import 'package:flutter_patcher_standalone/flutter_patcher_standalone.dart'
|
|
9
|
+
show
|
|
10
|
+
StandaloneRepositoryConfig,
|
|
11
|
+
StandaloneStorageConfig,
|
|
12
|
+
standaloneRepository,
|
|
13
|
+
standaloneStorage;
|
|
14
|
+
import 'package:http/http.dart' as http;
|
|
15
|
+
import 'package:http/testing.dart' show MockClient;
|
|
16
|
+
import 'package:test/test.dart';
|
|
17
|
+
|
|
18
|
+
/// A tiny in-memory router for the standalone REST contract.
|
|
19
|
+
Future<http.Response> _router(http.Request request) async {
|
|
20
|
+
final path = request.url.path;
|
|
21
|
+
final method = request.method;
|
|
22
|
+
|
|
23
|
+
if (path == '/api/bundles/channels' && method == 'GET') {
|
|
24
|
+
return http.Response(
|
|
25
|
+
jsonEncode({'data': {'channels': ['production', 'staging']}}),
|
|
26
|
+
200,
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
if (path == '/api/bundles' && method == 'GET') {
|
|
30
|
+
return http.Response(
|
|
31
|
+
jsonEncode({
|
|
32
|
+
'data': [
|
|
33
|
+
{
|
|
34
|
+
'id': 'b1',
|
|
35
|
+
'platform': 'android',
|
|
36
|
+
'channel': 'production',
|
|
37
|
+
'enabled': true,
|
|
38
|
+
'shouldForceUpdate': false,
|
|
39
|
+
'fileHash': 'h1',
|
|
40
|
+
'storageUri': 's3://b/b1/patch.zip',
|
|
41
|
+
'createdAt': '2024-01-01T00:00:00.000Z',
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
'pagination': {
|
|
45
|
+
'total': 1,
|
|
46
|
+
'hasNextPage': false,
|
|
47
|
+
'hasPreviousPage': false,
|
|
48
|
+
'currentPage': 1,
|
|
49
|
+
'totalPages': 1,
|
|
50
|
+
'nextCursor': null,
|
|
51
|
+
'previousCursor': null,
|
|
52
|
+
},
|
|
53
|
+
}),
|
|
54
|
+
200,
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
if (path == '/api/bundles' && method == 'POST') {
|
|
58
|
+
final body = jsonDecode(request.body) as List;
|
|
59
|
+
expect(body, hasLength(1));
|
|
60
|
+
expect((body.first as Map)['id'], 'b1');
|
|
61
|
+
return http.Response(jsonEncode({'success': true}), 201);
|
|
62
|
+
}
|
|
63
|
+
if (path.startsWith('/api/bundles/') && method == 'PATCH') {
|
|
64
|
+
expect(request.url.path, '/api/bundles/b1');
|
|
65
|
+
return http.Response(jsonEncode({'success': true}), 200);
|
|
66
|
+
}
|
|
67
|
+
if (path.startsWith('/api/bundles/') && method == 'DELETE') {
|
|
68
|
+
expect(request.url.path, '/api/bundles/b1');
|
|
69
|
+
return http.Response(jsonEncode({'success': true}), 200);
|
|
70
|
+
}
|
|
71
|
+
if (path.startsWith('/api/bundles/') && method == 'GET') {
|
|
72
|
+
return http.Response(
|
|
73
|
+
jsonEncode({
|
|
74
|
+
'id': 'b1',
|
|
75
|
+
'platform': 'android',
|
|
76
|
+
'channel': 'production',
|
|
77
|
+
'enabled': true,
|
|
78
|
+
'shouldForceUpdate': false,
|
|
79
|
+
'fileHash': 'h1',
|
|
80
|
+
'storageUri': 's3://b/b1/patch.zip',
|
|
81
|
+
'createdAt': '2024-01-01T00:00:00.000Z',
|
|
82
|
+
}),
|
|
83
|
+
200,
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
if (path == '/api/fingerprint/android/fp/production/00000000-0000-0000-0000-000000000000/b1' &&
|
|
87
|
+
method == 'GET') {
|
|
88
|
+
return http.Response(
|
|
89
|
+
jsonEncode({
|
|
90
|
+
'id': 'b2',
|
|
91
|
+
'shouldForceUpdate': false,
|
|
92
|
+
'status': 'UPDATE',
|
|
93
|
+
'fileUrl': 'https://cdn.example.com/b2/patch.zip',
|
|
94
|
+
'fileHash': 'h2',
|
|
95
|
+
}),
|
|
96
|
+
200,
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
if (path == '/api/upload' && method == 'POST') {
|
|
100
|
+
expect(request.url.queryParameters['key'], 'b1/patch.zip');
|
|
101
|
+
return http.Response(
|
|
102
|
+
jsonEncode({'storageUri': 's3://b/b1/patch.zip'}),
|
|
103
|
+
201,
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
if (path == '/api/getDownloadUrl' && method == 'POST') {
|
|
107
|
+
final body = jsonDecode(request.body) as Map;
|
|
108
|
+
expect(body['storageUri'], 's3://b/b1/patch.zip');
|
|
109
|
+
return http.Response(
|
|
110
|
+
jsonEncode({'fileUrl': 'https://cdn.example.com/b1/patch.zip'}),
|
|
111
|
+
200,
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
if (path == '/api/delete' && method == 'DELETE') {
|
|
115
|
+
final body = jsonDecode(request.body) as Map;
|
|
116
|
+
expect(body['storageUri'], 's3://b/b1/patch.zip');
|
|
117
|
+
return http.Response(jsonEncode({'success': true}), 200);
|
|
118
|
+
}
|
|
119
|
+
if (path == '/api/readText' && method == 'POST') {
|
|
120
|
+
final body = jsonDecode(request.body) as Map;
|
|
121
|
+
expect(body['storageUri'], 's3://manifest');
|
|
122
|
+
return http.Response(jsonEncode({'data': 'manifest-content'}), 200);
|
|
123
|
+
}
|
|
124
|
+
if (path == '/api/list' && method == 'GET') {
|
|
125
|
+
return http.Response(
|
|
126
|
+
jsonEncode({
|
|
127
|
+
'data': [
|
|
128
|
+
{'key': 'b1/patch.zip', 'storageUri': 's3://b/b1/patch.zip', 'size': 3},
|
|
129
|
+
],
|
|
130
|
+
}),
|
|
131
|
+
200,
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
if (path == '/api/_file' && method == 'GET') {
|
|
135
|
+
return http.Response('zip', 200);
|
|
136
|
+
}
|
|
137
|
+
if (request.url.host == 'cdn.example.com' && method == 'GET') {
|
|
138
|
+
return http.Response('zip-bytes', 200);
|
|
139
|
+
}
|
|
140
|
+
return http.Response(jsonEncode({'error': 'not found'}), 404);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
void main() {
|
|
144
|
+
late MockClient client;
|
|
145
|
+
late StandaloneRepositoryConfig repoConfig;
|
|
146
|
+
late StandaloneStorageConfig storageConfig;
|
|
147
|
+
|
|
148
|
+
setUp(() {
|
|
149
|
+
client = MockClient(_router);
|
|
150
|
+
repoConfig = StandaloneRepositoryConfig(
|
|
151
|
+
baseUrl: 'http://localhost',
|
|
152
|
+
clientFactory: () => client,
|
|
153
|
+
);
|
|
154
|
+
storageConfig = StandaloneStorageConfig(
|
|
155
|
+
baseUrl: 'http://localhost',
|
|
156
|
+
clientFactory: () => client,
|
|
157
|
+
);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
group('standaloneRepository', () {
|
|
161
|
+
test('getChannels', () async {
|
|
162
|
+
final db = standaloneRepository(repoConfig)();
|
|
163
|
+
final channels = await db.getChannels();
|
|
164
|
+
expect(channels, ['production', 'staging']);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
test('getBundleById', () async {
|
|
168
|
+
final db = standaloneRepository(repoConfig)();
|
|
169
|
+
final bundle = await db.getBundleById('b1');
|
|
170
|
+
expect(bundle?.id, 'b1');
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
test('getBundles', () async {
|
|
174
|
+
final db = standaloneRepository(repoConfig)();
|
|
175
|
+
final res = await db.getBundles(
|
|
176
|
+
const DatabaseBundleQueryOptions(where: null),
|
|
177
|
+
);
|
|
178
|
+
expect(res.data, hasLength(1));
|
|
179
|
+
expect(res.data.first.id, 'b1');
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
test('commitBundle insert/update/delete', () async {
|
|
183
|
+
final db = standaloneRepository(repoConfig)();
|
|
184
|
+
await db.appendBundle(
|
|
185
|
+
Bundle(
|
|
186
|
+
id: 'b1',
|
|
187
|
+
platform: Platform.android,
|
|
188
|
+
channel: 'production',
|
|
189
|
+
enabled: true,
|
|
190
|
+
shouldForceUpdate: false,
|
|
191
|
+
fileHash: 'h1',
|
|
192
|
+
storageUri: 's3://b/b1/patch.zip',
|
|
193
|
+
),
|
|
194
|
+
);
|
|
195
|
+
await db.commitBundle();
|
|
196
|
+
// update + delete exercise PATCH/DELETE routes via a fresh change set.
|
|
197
|
+
await db.updateBundle(
|
|
198
|
+
'b1',
|
|
199
|
+
{'enabled': false, 'channel': 'production'},
|
|
200
|
+
);
|
|
201
|
+
await db.commitBundle();
|
|
202
|
+
final existing = await db.getBundleById('b1');
|
|
203
|
+
await db.deleteBundle(existing!);
|
|
204
|
+
await db.commitBundle();
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
test('getUpdateInfo resolves update-available', () async {
|
|
208
|
+
final db = standaloneRepository(repoConfig)();
|
|
209
|
+
final info = await db.getUpdateInfo(
|
|
210
|
+
FingerprintGetBundlesArgs(
|
|
211
|
+
platform: Platform.android,
|
|
212
|
+
bundleId: 'b1',
|
|
213
|
+
fingerprintHash: 'fp',
|
|
214
|
+
),
|
|
215
|
+
);
|
|
216
|
+
expect(info, isNotNull);
|
|
217
|
+
expect(info!.id, 'b2');
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
group('standaloneStorage', () {
|
|
222
|
+
test('upload returns storageUri', () async {
|
|
223
|
+
final storage = standaloneStorage(storageConfig);
|
|
224
|
+
final tmp = File('${Directory.systemTemp.path}/fp_test.zip')
|
|
225
|
+
..writeAsStringSync('zip');
|
|
226
|
+
final result = await storage.profiles.node!.upload('b1/patch.zip', tmp.path);
|
|
227
|
+
expect(result['storageUri'], 's3://b/b1/patch.zip');
|
|
228
|
+
await tmp.delete();
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
test('getDownloadUrl returns fileUrl', () async {
|
|
232
|
+
final storage = standaloneStorage(storageConfig);
|
|
233
|
+
final result =
|
|
234
|
+
await storage.profiles.runtime!.getDownloadUrl('s3://b/b1/patch.zip');
|
|
235
|
+
expect(result['fileUrl'], 'https://cdn.example.com/b1/patch.zip');
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
test('delete succeeds', () async {
|
|
239
|
+
final storage = standaloneStorage(storageConfig);
|
|
240
|
+
await storage.profiles.node!.delete('s3://b/b1/patch.zip');
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
test('readText returns content', () async {
|
|
244
|
+
final storage = standaloneStorage(storageConfig);
|
|
245
|
+
final text = await storage.profiles.runtime!.readText('s3://manifest');
|
|
246
|
+
expect(text, 'manifest-content');
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
test('downloadFile fetches bytes', () async {
|
|
250
|
+
final storage = standaloneStorage(storageConfig);
|
|
251
|
+
final out = '${Directory.systemTemp.path}/fp_dl.zip';
|
|
252
|
+
await storage.profiles.node!.downloadFile('s3://b/b1/patch.zip', out);
|
|
253
|
+
expect(File(out).readAsStringSync(), 'zip-bytes');
|
|
254
|
+
await File(out).delete();
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
test('listObjects returns entries', () async {
|
|
258
|
+
final storage = standaloneStorage(storageConfig);
|
|
259
|
+
final objects = await storage.profiles.node!.listObjects();
|
|
260
|
+
expect(objects, hasLength(1));
|
|
261
|
+
expect(objects.first.key, 'b1/patch.zip');
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
}
|