56e5655353
Change-Id: Iaeb2152d47df05434ab470901900da56cae7e792 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/196125 Reviewed-by: Brian Wilkerson <brianwilkerson@google.com> Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
308 lines
9.6 KiB
Dart
308 lines
9.6 KiB
Dart
// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
|
|
// for details. All rights reserved. Use of this source code is governed by a
|
|
// BSD-style license that can be found in the LICENSE file.
|
|
|
|
import 'package:analysis_server/src/services/pub/pub_api.dart';
|
|
import 'package:analysis_server/src/services/pub/pub_package_service.dart';
|
|
import 'package:analyzer/instrumentation/service.dart';
|
|
import 'package:http/http.dart';
|
|
import 'package:test/test.dart';
|
|
import 'package:test_reflective_loader/test_reflective_loader.dart';
|
|
|
|
import '../mocks.dart';
|
|
import 'server_abstract.dart';
|
|
|
|
void main() {
|
|
defineReflectiveSuite(() {
|
|
defineReflectiveTests(PubApiTest);
|
|
defineReflectiveTests(PubPackageServiceTest);
|
|
});
|
|
}
|
|
|
|
@reflectiveTest
|
|
class PubApiTest {
|
|
static const pubDefaultUrl = 'https://pub.dartlang.org';
|
|
|
|
Uri? lastCalledUrl;
|
|
late MockHttpClient httpClient;
|
|
|
|
Future<void> check_pubHostedUrl(String? envValue, String expectedBase) async {
|
|
final api =
|
|
PubApi(InstrumentationService.NULL_SERVICE, httpClient, envValue);
|
|
await api.allPackages();
|
|
expect(lastCalledUrl.toString(),
|
|
equals('$expectedBase/api/package-name-completion-data'));
|
|
}
|
|
|
|
void setUp() {
|
|
httpClient = MockHttpClient();
|
|
lastCalledUrl = null;
|
|
httpClient.sendHandler = (BaseRequest request) async {
|
|
lastCalledUrl = request.url;
|
|
return Response('{}', 200);
|
|
};
|
|
}
|
|
|
|
Future<void> test_envPubHostedUrl_emptyString() =>
|
|
check_pubHostedUrl('', pubDefaultUrl);
|
|
|
|
Future<void> test_envPubHostedUrl_invalidUrl() =>
|
|
check_pubHostedUrl('test', pubDefaultUrl);
|
|
|
|
Future<void> test_envPubHostedUrl_missingScheme() =>
|
|
// It's hard to tell that this is intended to be a valid URL minus the scheme
|
|
// so it will fail validation and fall back to the default.
|
|
check_pubHostedUrl('pub.example.org', pubDefaultUrl);
|
|
|
|
Future<void> test_envPubHostedUrl_null() =>
|
|
check_pubHostedUrl(null, pubDefaultUrl);
|
|
|
|
Future<void> test_envPubHostedUrl_valid() =>
|
|
check_pubHostedUrl('https://pub.example.org', 'https://pub.example.org');
|
|
|
|
Future<void> test_envPubHostedUrl_validTrailingSlash() =>
|
|
check_pubHostedUrl('https://pub.example.org/', 'https://pub.example.org');
|
|
|
|
Future<void> test_httpClient_closesOwn() async {
|
|
final api = PubApi(InstrumentationService.NULL_SERVICE, null, null);
|
|
api.close();
|
|
expect(() => api.httpClient.get(Uri.parse('https://www.google.co.uk/')),
|
|
throwsA(anything));
|
|
}
|
|
|
|
Future<void> test_httpClient_doesNotCloseProvided() async {
|
|
final api = PubApi(InstrumentationService.NULL_SERVICE, httpClient, null);
|
|
api.close();
|
|
expect(httpClient.wasClosed, isFalse);
|
|
}
|
|
}
|
|
|
|
@reflectiveTest
|
|
class PubPackageServiceTest extends AbstractLspAnalysisServerTest {
|
|
/// A sample API response for package names. This should match the JSON served
|
|
/// at https://pub.dev/api/package-name-completion-data.
|
|
static const samplePackageList = '''
|
|
{ "packages": ["one", "two", "three"] }
|
|
''';
|
|
|
|
void expectPackages(List<String> packageNames) => expect(
|
|
server.pubPackageService.cachedPackages.map((p) => p.packageName),
|
|
equals(packageNames));
|
|
|
|
void providePubApiPackageList(String jsonResponse) {
|
|
httpClient.sendHandler = (BaseRequest request) async {
|
|
if (request.url.toString().endsWith(PubApi.packageNameListPath)) {
|
|
return Response(jsonResponse, 200);
|
|
} else {
|
|
throw UnimplementedError();
|
|
}
|
|
};
|
|
}
|
|
|
|
@override
|
|
void setUp() {
|
|
super.setUp();
|
|
// Cause retries to run immediately.
|
|
PubApi.failedRetryInitialDelaySeconds = 0;
|
|
}
|
|
|
|
Future<void> test_packageCache_diskCacheReplacedIfStale() async {
|
|
// This test should mirror test_packageCache_diskCacheUsedIfFresh
|
|
// besides the lastUpdated timestamp + expectations.
|
|
providePubApiPackageList(samplePackageList);
|
|
|
|
// Write a cache that should be considered stale.
|
|
server.pubPackageService.writeDiskCache(
|
|
PackageDetailsCache.fromApiResults([PubApiPackage('stalePackage1')])
|
|
..lastUpdatedUtc = DateTime.utc(1990),
|
|
);
|
|
|
|
await initialize();
|
|
await openFile(pubspecFileUri, '');
|
|
await pumpEventQueue();
|
|
|
|
// Expect the sample list to have overwritten the stale cache.
|
|
expectPackages(['one', 'two', 'three']);
|
|
}
|
|
|
|
Future<void> test_packageCache_diskCacheUsedIfFresh() async {
|
|
// This test should mirror test_packageCache_diskCacheReplacedIfStale
|
|
// besides the lastUpdated timestamp + expectations.
|
|
providePubApiPackageList(samplePackageList);
|
|
|
|
// Write a cache that is not stale.
|
|
server.pubPackageService.writeDiskCache(
|
|
PackageDetailsCache.fromApiResults([PubApiPackage('freshPackage1')]));
|
|
|
|
await initialize();
|
|
await openFile(pubspecFileUri, '');
|
|
await pumpEventQueue();
|
|
|
|
// Expect the fresh cache to still be used.
|
|
expectPackages(['freshPackage1']);
|
|
}
|
|
|
|
Future<void> test_packageCache_doesNotRetryOn400() async {
|
|
var requestNum = 1;
|
|
httpClient.sendHandler = (BaseRequest request) async {
|
|
if (request.url.toString().endsWith(PubApi.packageNameListPath)) {
|
|
return requestNum++ == 1
|
|
? Response('ERROR', 400)
|
|
: Response(samplePackageList, 200);
|
|
} else {
|
|
throw UnimplementedError();
|
|
}
|
|
};
|
|
|
|
await initialize();
|
|
expectPackages([]);
|
|
|
|
await openFile(pubspecFileUri, '');
|
|
await pumpEventQueue();
|
|
|
|
expectPackages([]);
|
|
expect(httpClient.sendHandlerCalls, equals(1));
|
|
}
|
|
|
|
Future<void> test_packageCache_doesNotRetryUnknownException() async {
|
|
httpClient.sendHandler =
|
|
(BaseRequest request) async => throw UnimplementedError();
|
|
|
|
await initialize();
|
|
expectPackages([]);
|
|
|
|
await openFile(pubspecFileUri, '');
|
|
await pumpEventQueue();
|
|
|
|
expectPackages([]);
|
|
expect(httpClient.sendHandlerCalls, equals(1));
|
|
}
|
|
|
|
Future<void> test_packageCache_fetchesFromServer() async {
|
|
// Provide the sample packages in the web request.
|
|
providePubApiPackageList(samplePackageList);
|
|
|
|
await initialize();
|
|
expectPackages([]);
|
|
|
|
await openFile(pubspecFileUri, '');
|
|
await pumpEventQueue();
|
|
|
|
expectPackages(['one', 'two', 'three']);
|
|
}
|
|
|
|
Future<void> test_packageCache_initializesOnPubspecOpen() async {
|
|
await initialize();
|
|
|
|
expect(server.pubPackageService.isRunning, isFalse);
|
|
expect(server.pubPackageService.packageCache, isNull);
|
|
expectPackages([]);
|
|
await openFile(pubspecFileUri, '');
|
|
await pumpEventQueue();
|
|
|
|
expect(server.pubPackageService.isRunning, isTrue);
|
|
expect(server.pubPackageService.packageCache, isNotNull);
|
|
expectPackages([]);
|
|
}
|
|
|
|
Future<void> test_packageCache_readsDiskCache() async {
|
|
server.pubPackageService.writeDiskCache(
|
|
PackageDetailsCache.fromApiResults([PubApiPackage('package1')]));
|
|
|
|
await initialize();
|
|
expectPackages([]);
|
|
|
|
await openFile(pubspecFileUri, '');
|
|
await pumpEventQueue();
|
|
|
|
expectPackages(['package1']);
|
|
}
|
|
|
|
Future<void> test_packageCache_retriesOn500() async {
|
|
var requestNum = 1;
|
|
httpClient.sendHandler = (BaseRequest request) async {
|
|
if (request.url.toString().endsWith(PubApi.packageNameListPath)) {
|
|
return requestNum++ == 1
|
|
? Response('ERROR', 500)
|
|
: Response(samplePackageList, 200);
|
|
} else {
|
|
throw UnimplementedError();
|
|
}
|
|
};
|
|
|
|
await initialize();
|
|
expectPackages([]);
|
|
|
|
await openFile(pubspecFileUri, '');
|
|
await pumpEventQueue();
|
|
|
|
expectPackages(['one', 'two', 'three']);
|
|
expect(httpClient.sendHandlerCalls, equals(2));
|
|
}
|
|
|
|
Future<void> test_packageCache_retriesOnInvalidJson() async {
|
|
var requestNum = 1;
|
|
httpClient.sendHandler = (BaseRequest request) async {
|
|
if (request.url.toString().endsWith(PubApi.packageNameListPath)) {
|
|
return requestNum++ == 1
|
|
? Response('$samplePackageList{{{{{{{', 200)
|
|
: Response(samplePackageList, 200);
|
|
} else {
|
|
throw UnimplementedError();
|
|
}
|
|
};
|
|
|
|
await initialize();
|
|
expectPackages([]);
|
|
|
|
await openFile(pubspecFileUri, '');
|
|
await pumpEventQueue();
|
|
|
|
expectPackages(['one', 'two', 'three']);
|
|
expect(httpClient.sendHandlerCalls, equals(2));
|
|
}
|
|
|
|
Future<void> test_packageCache_timeRemaining() async {
|
|
void expectHoursRemaining(DateTime cacheTime, int expectedHoursRemaining) {
|
|
final cache = PackageDetailsCache.empty();
|
|
cache.lastUpdatedUtc = cacheTime.toUtc();
|
|
|
|
final remainingHours = cache.cacheTimeRemaining.inHours;
|
|
expect(remainingHours, isNonNegative);
|
|
expect(remainingHours, closeTo(expectedHoursRemaining, 1));
|
|
}
|
|
|
|
final maxHours = PackageDetailsCache.maxCacheAge.inHours;
|
|
|
|
// Very old cache should have no time remaining.
|
|
expectHoursRemaining(DateTime(2020, 12, 1), 0);
|
|
|
|
// Cache from 1 hour ago should max-1 hours remaining.
|
|
expectHoursRemaining(DateTime.now().add(Duration(hours: -1)), maxHours - 1);
|
|
|
|
// Cache from 10 hours ago should max-10 hours remaining.
|
|
expectHoursRemaining(
|
|
DateTime.now().add(Duration(hours: -10)), maxHours - 10);
|
|
|
|
// Cache from maxAge ago should have no hours remaining.
|
|
expectHoursRemaining(
|
|
DateTime.now().add(-PackageDetailsCache.maxCacheAge), 0);
|
|
}
|
|
|
|
Future<void> test_packageCache_writesDiskCache() async {
|
|
// Provide the sample packages in the web request.
|
|
providePubApiPackageList(samplePackageList);
|
|
|
|
await initialize();
|
|
expect(server.pubPackageService.readDiskCache(), isNull);
|
|
|
|
await openFile(pubspecFileUri, '');
|
|
await pumpEventQueue();
|
|
|
|
final cache = server.pubPackageService.readDiskCache()!;
|
|
final packages = cache.packages.values.toList();
|
|
|
|
expect(packages.map((p) => p.packageName), equals(['one', 'two', 'three']));
|
|
}
|
|
}
|