Skip to content

Commit 31647b9

Browse files
authored
Typing for ExportedApi and minimal test (#8167)
* Typing for ExportedApi and minimal test * Use clock.now() * Added headers to files
1 parent 7b5fec4 commit 31647b9

File tree

2 files changed

+108
-6
lines changed

2 files changed

+108
-6
lines changed

app/lib/package/api_export/exported_api.dart

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
1+
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
15
import 'dart:async';
26
import 'dart:convert';
37
import 'dart:io';
48

9+
import 'package:_pub_shared/data/advisories_api.dart';
10+
import 'package:_pub_shared/data/package_api.dart';
511
import 'package:clock/clock.dart';
612
import 'package:gcloud/storage.dart';
713
import 'package:logging/logging.dart';
@@ -43,7 +49,8 @@ final class ExportedApi {
4349
ExportedPackage._(this, packageName);
4450

4551
/// Interface for writing `/api/package-name-completion-data`
46-
ExportedJsonFile get packageNameCompletionData => ExportedJsonFile._(
52+
ExportedJsonFile<Map<String, Object?>> get packageNameCompletionData =>
53+
ExportedJsonFile<Map<String, Object?>>._(
4754
this,
4855
'/api/package-name-completion-data',
4956
Duration(hours: 8),
@@ -178,7 +185,7 @@ final class ExportedPackage {
178185

179186
ExportedPackage._(this._owner, this._package);
180187

181-
ExportedJsonFile _suffix(String suffix) => ExportedJsonFile._(
188+
ExportedJsonFile<T> _suffix<T>(String suffix) => ExportedJsonFile<T>._(
182189
_owner,
183190
'/api/packages/$_package$suffix',
184191
Duration(minutes: 10),
@@ -187,10 +194,11 @@ final class ExportedPackage {
187194
/// Interface for writing `/api/packages/<package>`.
188195
///
189196
/// Which contains version listing information.
190-
ExportedJsonFile get versions => _suffix('');
197+
ExportedJsonFile<PackageData> get versions => _suffix<PackageData>('');
191198

192199
/// Interface for writing `/api/packages/<package>/advisories`.
193-
ExportedJsonFile get advisories => _suffix('/advisories');
200+
ExportedJsonFile<ListAdvisoriesResponse> get advisories =>
201+
_suffix<ListAdvisoriesResponse>('/advisories');
194202

195203
/// Interace for writing `/api/archives/<package>-<version>.tar.gz`.
196204
ExportedBlob tarball(String version) => ExportedBlob._(
@@ -239,7 +247,7 @@ sealed class ExportedObject {
239247
/// * `Content-Type`,
240248
/// * `Content-Encoding`, and,
241249
/// * `Cache-Control`.
242-
final class ExportedJsonFile extends ExportedObject {
250+
final class ExportedJsonFile<T> extends ExportedObject {
243251
static final _jsonGzip = json.fuse(utf8).fuse(gzip);
244252
final Duration _maxAge;
245253

@@ -256,7 +264,7 @@ final class ExportedJsonFile extends ExportedObject {
256264
);
257265

258266
/// Write [data] as gzipped JSON in UTF-8 format.
259-
Future<void> write(Map<String, Object?> data) async {
267+
Future<void> write(T data) async {
260268
final gzipped = _jsonGzip.encode(data);
261269
await Future.wait(_owner._prefixes.map((prefix) async {
262270
await _owner._pool.withResource(() async {
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'dart:convert';
6+
import 'dart:io';
7+
import 'dart:typed_data';
8+
9+
import 'package:_pub_shared/data/package_api.dart';
10+
import 'package:clock/clock.dart';
11+
import 'package:gcloud/storage.dart';
12+
import 'package:googleapis/storage/v1.dart' show DetailedApiRequestError;
13+
import 'package:pub_dev/package/api_export/exported_api.dart';
14+
import 'package:pub_dev/shared/storage.dart';
15+
import 'package:pub_dev/shared/utils.dart';
16+
import 'package:test/test.dart';
17+
import '../../shared/test_services.dart';
18+
19+
void main() {
20+
testWithFakeTime('ExportedApi', (fakeTime) async {
21+
await storageService.createBucket('exported-api');
22+
final bucket = storageService.bucket('exported-api');
23+
24+
/// Read bytes from bucket
25+
Future<Uint8List?> readBytes(String path) async {
26+
try {
27+
return await bucket.readAsBytes(path);
28+
} on DetailedApiRequestError catch (e) {
29+
if (e.status == 404) return null;
30+
rethrow;
31+
}
32+
}
33+
34+
/// Read gzipped JSON from bucket
35+
Future<Object?> readGzippedJson(String path) async {
36+
final bytes = await readBytes(path);
37+
if (bytes == null) {
38+
return null;
39+
}
40+
return utf8JsonDecoder.convert(gzip.decode(bytes));
41+
}
42+
43+
final exportedApi = ExportedApi(storageService, bucket);
44+
45+
// Test that deletion works when bucket is empty
46+
await exportedApi.package('retry').delete();
47+
48+
// Test that GC works when bucket is empty
49+
await exportedApi.garbageCollect({});
50+
51+
final retryPkgData1 = PackageData(
52+
name: 'retry',
53+
latest: VersionInfo(
54+
version: '1.2.3',
55+
retracted: false,
56+
pubspec: {},
57+
archiveUrl: '-',
58+
archiveSha256: '-',
59+
published: clock.now(),
60+
),
61+
versions: [],
62+
);
63+
64+
await exportedApi.package('retry').versions.write(retryPkgData1);
65+
66+
expect(
67+
await readGzippedJson('latest/api/packages/retry'),
68+
json.decode(json.encode(retryPkgData1.toJson())),
69+
);
70+
71+
// Check that GC after 10 mins won't delete a package we don't recognize
72+
fakeTime.elapseSync(minutes: 10);
73+
await exportedApi.garbageCollect({});
74+
expect(
75+
await readGzippedJson('latest/api/packages/retry'),
76+
isNotNull,
77+
);
78+
79+
// Check that GC after 2 days won't delete a package we know
80+
fakeTime.elapseSync(days: 2);
81+
await exportedApi.garbageCollect({'retry'});
82+
expect(
83+
await readGzippedJson('latest/api/packages/retry'),
84+
isNotNull,
85+
);
86+
87+
// Check retry after 2 days will delete a package we don't know.
88+
await exportedApi.garbageCollect({});
89+
expect(
90+
await readGzippedJson('latest/api/packages/retry'),
91+
isNull,
92+
);
93+
});
94+
}

0 commit comments

Comments
 (0)