feat(shorebird_code_push_api): endpoints and deployment (#2)
This commit is contained in:
@@ -20,6 +20,11 @@ on:
|
||||
- "packages/shorebird_code_push_api/test/**"
|
||||
- "packages/shorebird_code_push_api/pubspec.yaml"
|
||||
|
||||
env:
|
||||
PROJECT_ID: shorebird-code-push-api
|
||||
SERVICE: shorebird-code-push-api
|
||||
REGION: us-central1
|
||||
|
||||
jobs:
|
||||
semantic-pull-request:
|
||||
uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/semantic_pull_request.yml@v1
|
||||
@@ -33,3 +38,46 @@ jobs:
|
||||
uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/pana.yml@v1
|
||||
with:
|
||||
working_directory: packages/shorebird_code_push_api
|
||||
|
||||
deploy:
|
||||
needs: build
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: packages/shorebird_code_push_api
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Setup Cloud SDK
|
||||
uses: google-github-actions/setup-gcloud@v0.2.0
|
||||
with:
|
||||
project_id: ${{ env.PROJECT_ID }}
|
||||
service_account_key: ${{ secrets.CLOUD_RUN_SA }}
|
||||
export_default_credentials: true
|
||||
|
||||
- name: Authorize Docker Push
|
||||
run: gcloud auth configure-docker
|
||||
|
||||
- name: Build and Push Container
|
||||
run: |-
|
||||
docker build -t gcr.io/${{ env.PROJECT_ID }}/${{ env.SERVICE }}:${{ github.sha }} .
|
||||
docker push gcr.io/${{ env.PROJECT_ID }}/${{ env.SERVICE }}:${{ github.sha }}
|
||||
|
||||
- name: Deploy to Cloud Run
|
||||
id: deploy
|
||||
uses: google-github-actions/deploy-cloudrun@v0.4.0
|
||||
with:
|
||||
service: ${{ env.SERVICE }}
|
||||
image: gcr.io/${{ env.PROJECT_ID }}/${{ env.SERVICE }}:${{ github.sha }}
|
||||
region: ${{ env.REGION }}
|
||||
|
||||
- name: Show Output
|
||||
run: echo ${{ steps.deploy.outputs.url }}
|
||||
|
||||
- name: Ping
|
||||
run: curl "${{ steps.deploy.outputs.url }}"
|
||||
|
||||
@@ -4,4 +4,6 @@
|
||||
.dart_tool/
|
||||
.packages
|
||||
build/
|
||||
pubspec.lock
|
||||
pubspec.lock
|
||||
coverage/
|
||||
cache/
|
||||
@@ -1 +1,4 @@
|
||||
include: package:very_good_analysis/analysis_options.4.0.0.yaml
|
||||
linter:
|
||||
rules:
|
||||
public_member_api_docs: false
|
||||
|
||||
@@ -3,15 +3,24 @@ import 'dart:io';
|
||||
import 'package:shelf/shelf.dart';
|
||||
import 'package:shelf/shelf_io.dart' as shelf_io;
|
||||
import 'package:shelf_router/shelf_router.dart' as shelf_router;
|
||||
import 'package:shorebird_code_push_api/src/middleware/middleware.dart';
|
||||
import 'package:shorebird_code_push_api/src/routes/routes.dart';
|
||||
|
||||
Future<void> main() async {
|
||||
final port = int.parse(Platform.environment['PORT'] ?? '8080');
|
||||
final router = shelf_router.Router()
|
||||
..all('/', (_) => Response.ok('Hello, world!'));
|
||||
..all('/', (_) => Response(HttpStatus.noContent))
|
||||
..post('/api/v1/updates', checkForUpdatesHandler)
|
||||
..get('/api/v1/releases/<version>', downloadReleaseHandler)
|
||||
..post('/api/v1/releases', uploadReleaseHandler);
|
||||
|
||||
final handler = const Pipeline()
|
||||
.addMiddleware(versionStoreProvider)
|
||||
.addHandler(router.call);
|
||||
|
||||
final server = await shelf_io.serve(
|
||||
logRequests().addHandler(router.call),
|
||||
InternetAddress.anyIPv6,
|
||||
logRequests().addHandler(handler),
|
||||
InternetAddress.anyIPv4,
|
||||
port,
|
||||
);
|
||||
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
/// The Shorebird CodePush API
|
||||
library shorebird_code_push_api;
|
||||
|
||||
export 'src/shorebird_code_push_api.dart';
|
||||
@@ -0,0 +1 @@
|
||||
const cachePath = 'cache';
|
||||
@@ -0,0 +1 @@
|
||||
export 'version_store_provider.dart';
|
||||
@@ -0,0 +1,7 @@
|
||||
import 'package:shorebird_code_push_api/src/config.dart' as config;
|
||||
import 'package:shorebird_code_push_api/src/provider.dart';
|
||||
import 'package:shorebird_code_push_api/src/version_store.dart';
|
||||
|
||||
const _versionStore = VersionStore(cachePath: config.cachePath);
|
||||
|
||||
final versionStoreProvider = provider<VersionStore>((_) => _versionStore);
|
||||
@@ -0,0 +1,25 @@
|
||||
import 'package:shelf/shelf.dart';
|
||||
|
||||
extension ProvideExtension on Request {
|
||||
Request provide<T extends Object>(T Function() create) {
|
||||
return change(context: {...context, '$T': create});
|
||||
}
|
||||
|
||||
T lookup<T>() {
|
||||
final value = context['$T'];
|
||||
if (value == null) {
|
||||
throw StateError(
|
||||
'''
|
||||
request.lookup<$T>() called with a request request that does not contain a $T.
|
||||
''',
|
||||
);
|
||||
}
|
||||
return (value as T Function())();
|
||||
}
|
||||
}
|
||||
|
||||
Middleware provider<T extends Object>(T Function(Request request) create) {
|
||||
return (handler) {
|
||||
return (req) => handler(req.provide(() => create(req)));
|
||||
};
|
||||
}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
export 'check_for_updates_handler.dart';
|
||||
export 'models/models.dart';
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:shelf/shelf.dart';
|
||||
import 'package:shorebird_code_push_api/src/provider.dart';
|
||||
import 'package:shorebird_code_push_api/src/routes/check_for_updates/check_for_updates.dart';
|
||||
import 'package:shorebird_code_push_api/src/version_store.dart';
|
||||
|
||||
Future<Response> checkForUpdatesHandler(Request request) async {
|
||||
late final CheckForUpdatesRequest checkForUpdatesRequest;
|
||||
try {
|
||||
checkForUpdatesRequest = CheckForUpdatesRequest.fromJson(
|
||||
jsonDecode(await request.readAsString()) as Map<String, dynamic>,
|
||||
);
|
||||
} catch (_) {
|
||||
return Response.badRequest(
|
||||
body: 'Invalid request body',
|
||||
headers: {HttpHeaders.contentTypeHeader: ContentType.text.value},
|
||||
);
|
||||
}
|
||||
|
||||
final store = request.lookup<VersionStore>();
|
||||
final latestVersion = store.latestVersionForClient(
|
||||
checkForUpdatesRequest.clientId,
|
||||
currentVersion: checkForUpdatesRequest.version,
|
||||
);
|
||||
|
||||
final response = latestVersion == null
|
||||
? const CheckForUpdatesResponse()
|
||||
: CheckForUpdatesResponse(
|
||||
updateAvailable: true,
|
||||
update: Update(version: latestVersion, hash: ''),
|
||||
);
|
||||
|
||||
return Response.ok(
|
||||
json.encode(response.toJson()),
|
||||
headers: {HttpHeaders.contentTypeHeader: ContentType.json.value},
|
||||
);
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
class CheckForUpdatesRequest {
|
||||
const CheckForUpdatesRequest({
|
||||
required this.version,
|
||||
required this.platform,
|
||||
required this.arch,
|
||||
required this.clientId,
|
||||
});
|
||||
|
||||
factory CheckForUpdatesRequest.fromJson(Map<String, dynamic> json) {
|
||||
return CheckForUpdatesRequest(
|
||||
version: (json['version'] ?? '') as String,
|
||||
platform: json['platform'] as String,
|
||||
arch: json['arch'] as String,
|
||||
clientId: json['client_id'] as String,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'version': version,
|
||||
'platform': platform,
|
||||
'arch': arch,
|
||||
'client_id': clientId,
|
||||
};
|
||||
}
|
||||
|
||||
final String version;
|
||||
final String platform;
|
||||
final String arch;
|
||||
final String clientId;
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
class CheckForUpdatesResponse {
|
||||
const CheckForUpdatesResponse({
|
||||
this.updateAvailable = false,
|
||||
this.update,
|
||||
});
|
||||
|
||||
final bool updateAvailable;
|
||||
final Update? update;
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return <String, dynamic>{
|
||||
'update_available': updateAvailable,
|
||||
if (update != null) 'update': update!.toJson(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class Update {
|
||||
const Update({required this.version, required this.hash});
|
||||
|
||||
final String version;
|
||||
final String hash;
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return <String, dynamic>{
|
||||
'version': version,
|
||||
'hash': hash,
|
||||
'download_url': downloadUrlForVersion(version),
|
||||
};
|
||||
}
|
||||
|
||||
String downloadUrlForVersion(String version) {
|
||||
return 'http://localhost:8080/releases/$version.txt';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export 'check_for_updates_request.dart';
|
||||
export 'check_for_updates_response.dart';
|
||||
@@ -0,0 +1 @@
|
||||
export 'download_release_handler.dart';
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:shelf/shelf.dart';
|
||||
import 'package:shorebird_code_push_api/src/provider.dart';
|
||||
import 'package:shorebird_code_push_api/src/version_store.dart';
|
||||
|
||||
Future<Response> downloadReleaseHandler(
|
||||
Request request,
|
||||
String versionWithExtension,
|
||||
) async {
|
||||
final version = path.withoutExtension(versionWithExtension);
|
||||
final releasePath =
|
||||
request.lookup<VersionStore>().filePathForVersion(version);
|
||||
final file = File(releasePath);
|
||||
if (!file.existsSync()) return Response.notFound('Release not found');
|
||||
final bytes = file.openRead();
|
||||
return Response.ok(bytes);
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export 'check_for_updates/check_for_updates.dart';
|
||||
export 'download_release/download_release.dart';
|
||||
export 'upload_release/upload_release.dart';
|
||||
@@ -0,0 +1 @@
|
||||
export 'upload_release_handler.dart';
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:shelf/shelf.dart';
|
||||
import 'package:shelf_multipart/form_data.dart';
|
||||
import 'package:shelf_multipart/multipart.dart';
|
||||
import 'package:shorebird_code_push_api/src/provider.dart';
|
||||
import 'package:shorebird_code_push_api/src/version_store.dart';
|
||||
|
||||
Future<Response> uploadReleaseHandler(Request request) async {
|
||||
if (!request.isMultipart || !request.isMultipartForm) {
|
||||
return Response.badRequest(body: 'Expected multipart form request');
|
||||
}
|
||||
|
||||
final store = request.lookup<VersionStore>();
|
||||
|
||||
store.cacheDir.createSync(recursive: true);
|
||||
|
||||
final nextVersion = store.getNextVersion();
|
||||
final path = store.filePathForVersion(nextVersion);
|
||||
|
||||
var foundFile = false;
|
||||
|
||||
try {
|
||||
await for (final formData in request.multipartFormData) {
|
||||
// 'file' is just the name of the field we used in this form.
|
||||
if (formData.name == 'file') {
|
||||
if (foundFile) {
|
||||
throw Exception('Unexpected form data: ${formData.name}');
|
||||
}
|
||||
final file = File(path);
|
||||
await file.create();
|
||||
await file.writeAsBytes(await formData.part.readBytes(), flush: true);
|
||||
foundFile = true;
|
||||
continue;
|
||||
} else {
|
||||
throw Exception('Unexpected form data: ${formData.name}');
|
||||
}
|
||||
}
|
||||
if (!foundFile) throw Exception('Missing file');
|
||||
} catch (error) {
|
||||
return Response.badRequest(body: error.toString());
|
||||
}
|
||||
|
||||
return Response(HttpStatus.created, body: 'OK');
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
/// {@template shorebird_code_push_api}
|
||||
/// The Shorebird CodePush API
|
||||
/// {@endtemplate}
|
||||
class ShorebirdCodePushApi {
|
||||
/// {@macro shorebird_code_push_api}
|
||||
const ShorebirdCodePushApi();
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:version/version.dart';
|
||||
|
||||
int _compareVersions(String a, String b) {
|
||||
return Version.parse(a).compareTo(Version.parse(b));
|
||||
}
|
||||
|
||||
class VersionStore {
|
||||
const VersionStore({required this.cachePath});
|
||||
|
||||
final String cachePath;
|
||||
|
||||
Directory get cacheDir {
|
||||
return Directory(p.join(Directory.current.path, cachePath));
|
||||
}
|
||||
|
||||
// Should take an api key/product name, etc.
|
||||
String getNextVersion() {
|
||||
final latest = latestVersionForClient('client') ?? '0.0.0';
|
||||
final next = Version.parse(latest).incrementPatch().toString();
|
||||
return next;
|
||||
}
|
||||
|
||||
void addVersion(String version, List<int> bytes) {
|
||||
cacheDir.createSync(recursive: true);
|
||||
final path = filePathForVersion(version);
|
||||
File(path).writeAsBytesSync(bytes);
|
||||
}
|
||||
|
||||
String? latestVersionForClient(String clientId, {String? currentVersion}) {
|
||||
final versions = _versionsForClientId(clientId).toList()
|
||||
..sort(_compareVersions);
|
||||
if (versions.isEmpty) return null;
|
||||
if (versions.last == currentVersion) return null;
|
||||
|
||||
return versions.last;
|
||||
}
|
||||
|
||||
String filePathForVersion(String version) {
|
||||
return p.join(cacheDir.path, '$version.txt');
|
||||
}
|
||||
|
||||
Iterable<String> _versionsForClientId(String clientId) {
|
||||
// This should use the clientId to get a productId and look up the versions
|
||||
// based on productId/architecture, etc.
|
||||
try {
|
||||
final dir = cacheDir;
|
||||
final files = dir.listSync();
|
||||
return files.map((e) => p.basenameWithoutExtension(e.path));
|
||||
} catch (e) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,8 +7,11 @@ environment:
|
||||
sdk: ">=2.19.0 <3.0.0"
|
||||
|
||||
dependencies:
|
||||
path: ^1.8.3
|
||||
shelf: ^1.4.0
|
||||
shelf_multipart: ^1.0.0
|
||||
shelf_router: ^1.1.3
|
||||
version: ^3.0.2
|
||||
|
||||
dev_dependencies:
|
||||
mocktail: ^0.3.0
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import 'package:shelf/shelf.dart';
|
||||
import 'package:shorebird_code_push_api/src/middleware/middleware.dart';
|
||||
import 'package:shorebird_code_push_api/src/provider.dart';
|
||||
import 'package:shorebird_code_push_api/src/version_store.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
group('versionStoreProvider', () {
|
||||
test('provides a version store instance', () async {
|
||||
VersionStore? store;
|
||||
|
||||
final handler = versionStoreProvider(
|
||||
(req) {
|
||||
store = req.lookup<VersionStore>();
|
||||
return Response.ok('');
|
||||
},
|
||||
);
|
||||
final request = Request('GET', Uri.parse('http://localhost/'));
|
||||
|
||||
await handler(request);
|
||||
expect(store, isNotNull);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:shelf/shelf.dart';
|
||||
import 'package:shorebird_code_push_api/src/provider.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
test('values can be provided and read via middleware', () async {
|
||||
const value = '__test_value__';
|
||||
Handler middleware(Handler handler) {
|
||||
return (request) {
|
||||
return handler(request.provide(() => value));
|
||||
};
|
||||
}
|
||||
|
||||
Response onRequest(Request request) {
|
||||
final value = request.lookup<String>();
|
||||
return Response.ok(value);
|
||||
}
|
||||
|
||||
final handler =
|
||||
const Pipeline().addMiddleware(middleware).addHandler(onRequest);
|
||||
|
||||
final request = Request('GET', Uri.parse('http://localhost/'));
|
||||
final response = await handler(request);
|
||||
|
||||
await expectLater(response.statusCode, equals(HttpStatus.ok));
|
||||
await expectLater(await response.readAsString(), equals(value));
|
||||
});
|
||||
|
||||
test('A StateError is thrown when reading an un-provided value', () async {
|
||||
Response onRequest(Request request) {
|
||||
request.lookup<Uri>();
|
||||
return Response.ok('');
|
||||
}
|
||||
|
||||
final handler = const Pipeline()
|
||||
.addMiddleware((handler) => handler)
|
||||
.addHandler(onRequest);
|
||||
|
||||
final request = Request('GET', Uri.parse('http://localhost/'));
|
||||
|
||||
await expectLater(() => handler(request), throwsStateError);
|
||||
});
|
||||
}
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
import 'package:shelf/shelf.dart';
|
||||
import 'package:shorebird_code_push_api/src/provider.dart';
|
||||
import 'package:shorebird_code_push_api/src/routes/check_for_updates/check_for_updates.dart';
|
||||
import 'package:shorebird_code_push_api/src/version_store.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
class _MockVersionStore extends Mock implements VersionStore {}
|
||||
|
||||
void main() {
|
||||
group('checkForUpdatesHandler', () {
|
||||
final uri = Uri.parse('http://localhost/');
|
||||
|
||||
late VersionStore store;
|
||||
|
||||
setUp(() {
|
||||
store = _MockVersionStore();
|
||||
});
|
||||
|
||||
test('returns 400 if request body is invalid', () async {
|
||||
final request = Request('POST', uri);
|
||||
final response = await checkForUpdatesHandler(request);
|
||||
|
||||
expect(response.statusCode, HttpStatus.badRequest);
|
||||
});
|
||||
|
||||
test(
|
||||
'returns 200 no update available '
|
||||
'when unable to get latest version', () async {
|
||||
const payload = CheckForUpdatesRequest(
|
||||
version: '1.0.0',
|
||||
platform: 'android',
|
||||
arch: 'arm64',
|
||||
clientId: 'client-id',
|
||||
);
|
||||
|
||||
when(
|
||||
() => store.latestVersionForClient(
|
||||
any(),
|
||||
currentVersion: any(named: 'currentVersion'),
|
||||
),
|
||||
).thenReturn(null);
|
||||
|
||||
final request = Request(
|
||||
'POST',
|
||||
uri,
|
||||
body: json.encode(payload.toJson()),
|
||||
).provide(() => store);
|
||||
|
||||
final response = await checkForUpdatesHandler(request);
|
||||
|
||||
expect(response.statusCode, HttpStatus.ok);
|
||||
|
||||
final body = await response.readAsString();
|
||||
expect(body, equals('{"update_available":false}'));
|
||||
});
|
||||
|
||||
test('returns 200 update available when version is not latest', () async {
|
||||
const payload = CheckForUpdatesRequest(
|
||||
version: '1.0.0',
|
||||
platform: 'android',
|
||||
arch: 'arm64',
|
||||
clientId: 'client-id',
|
||||
);
|
||||
|
||||
when(
|
||||
() => store.latestVersionForClient(
|
||||
any(),
|
||||
currentVersion: any(named: 'currentVersion'),
|
||||
),
|
||||
).thenReturn('1.0.1');
|
||||
|
||||
final request = Request(
|
||||
'POST',
|
||||
uri,
|
||||
body: json.encode(payload.toJson()),
|
||||
).provide(() => store);
|
||||
|
||||
final response = await checkForUpdatesHandler(request);
|
||||
|
||||
expect(response.statusCode, HttpStatus.ok);
|
||||
|
||||
final body = await response.readAsString();
|
||||
expect(
|
||||
body,
|
||||
equals(
|
||||
'{"update_available":true,"update":{"version":"1.0.1","hash":"","download_url":"http://localhost:8080/releases/1.0.1.txt"}}',
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:shelf/shelf.dart';
|
||||
import 'package:shorebird_code_push_api/src/provider.dart';
|
||||
import 'package:shorebird_code_push_api/src/routes/download_release/download_release.dart';
|
||||
import 'package:shorebird_code_push_api/src/version_store.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
class _MockVersionStore extends Mock implements VersionStore {}
|
||||
|
||||
void main() {
|
||||
group('downloadReleaseHandler', () {
|
||||
final uri = Uri.parse('http://localhost/');
|
||||
late VersionStore store;
|
||||
|
||||
setUp(() {
|
||||
store = _MockVersionStore();
|
||||
});
|
||||
|
||||
test('returns 404 if release not found', () async {
|
||||
when(() => store.filePathForVersion('1.0.0')).thenReturn('not-found');
|
||||
|
||||
final request = Request('GET', uri).provide(() => store);
|
||||
final response = await downloadReleaseHandler(request, '1.0.0.txt');
|
||||
|
||||
expect(response.statusCode, HttpStatus.notFound);
|
||||
});
|
||||
|
||||
test('returns 200 if release found', () async {
|
||||
when(
|
||||
() => store.filePathForVersion('1.0.0'),
|
||||
).thenReturn(path.join('test', 'fixtures', 'release.txt'));
|
||||
|
||||
final request = Request('GET', uri).provide(() => store);
|
||||
final response = await downloadReleaseHandler(request, '1.0.0.txt');
|
||||
|
||||
expect(response.statusCode, HttpStatus.ok);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
// ignore_for_file: prefer_const_constructors
|
||||
import 'package:shorebird_code_push_api/shorebird_code_push_api.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
group('ShorebirdCodePushApi', () {
|
||||
test('can be instantiated', () {
|
||||
expect(ShorebirdCodePushApi(), isNotNull);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:shorebird_code_push_api/src/version_store.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
group('VersionStore', () {
|
||||
group('getNextVersion', () {
|
||||
test('returns 0.0.1 when no versions exist', () {
|
||||
final tempDir = Directory.systemTemp.createTempSync();
|
||||
final store = VersionStore(cachePath: tempDir.path);
|
||||
expect(store.getNextVersion(), equals('0.0.1'));
|
||||
});
|
||||
|
||||
test('returns 0.0.2 when 0.0.1 exists', () {
|
||||
final tempDir = Directory.systemTemp.createTempSync();
|
||||
final store = VersionStore(cachePath: tempDir.path)
|
||||
..addVersion('0.0.1', []);
|
||||
expect(store.getNextVersion(), equals('0.0.2'));
|
||||
});
|
||||
});
|
||||
|
||||
group('latestVersionForClient', () {
|
||||
test('returns null when cache does not exist', () {
|
||||
const store = VersionStore(cachePath: 'invalid-path');
|
||||
expect(store.latestVersionForClient('empty-client-id'), isNull);
|
||||
});
|
||||
|
||||
test('returns null when no versions exist', () {
|
||||
final tempDir = Directory.systemTemp.createTempSync();
|
||||
final store = VersionStore(cachePath: tempDir.path);
|
||||
expect(store.latestVersionForClient('empty-client-id'), isNull);
|
||||
});
|
||||
|
||||
test('returns latest version when multiple versions exist', () {
|
||||
final tempDir = Directory.systemTemp.createTempSync();
|
||||
final store = VersionStore(cachePath: tempDir.path)
|
||||
..addVersion('0.0.1', [])
|
||||
..addVersion('0.0.2', []);
|
||||
expect(
|
||||
store.latestVersionForClient('empty-client-id'),
|
||||
equals('0.0.2'),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user