diff --git a/packages/shorebird_code_push_protocol/lib/src/messages/messages.dart b/packages/shorebird_code_push_protocol/lib/src/messages/messages.dart index 89311ef6..a5d6e434 100644 --- a/packages/shorebird_code_push_protocol/lib/src/messages/messages.dart +++ b/packages/shorebird_code_push_protocol/lib/src/messages/messages.dart @@ -13,6 +13,7 @@ export 'get_organizations/get_organizations.dart'; export 'get_release_artifacts/get_release_artifacts.dart'; export 'get_release_patches/get_release_patches.dart'; export 'get_releases/get_releases.dart'; +export 'patch_check/patch_check.dart'; export 'promote_patch/promote_patch.dart'; export 'update_app_collaborator/update_app_collaborator.dart'; export 'update_patch/update_patch.dart'; diff --git a/packages/shorebird_code_push_protocol/lib/src/messages/patch_check/patch_check.dart b/packages/shorebird_code_push_protocol/lib/src/messages/patch_check/patch_check.dart new file mode 100644 index 00000000..3bb2106f --- /dev/null +++ b/packages/shorebird_code_push_protocol/lib/src/messages/patch_check/patch_check.dart @@ -0,0 +1,2 @@ +export 'patch_check_request.dart'; +export 'patch_check_response.dart'; diff --git a/packages/shorebird_code_push_protocol/lib/src/messages/patch_check/patch_check_request.dart b/packages/shorebird_code_push_protocol/lib/src/messages/patch_check/patch_check_request.dart new file mode 100644 index 00000000..ce0c42f7 --- /dev/null +++ b/packages/shorebird_code_push_protocol/lib/src/messages/patch_check/patch_check_request.dart @@ -0,0 +1,52 @@ +import 'package:json_annotation/json_annotation.dart'; +import 'package:shorebird_code_push_protocol/shorebird_code_push_protocol.dart'; + +part 'patch_check_request.g.dart'; + +/// {@template patch_check_request} +/// The request body for POST /api/v1/patches/check +/// {@endtemplate} +@JsonSerializable() +class PatchCheckRequest { + /// {@macro patch_check_request} + const PatchCheckRequest({ + required this.releaseVersion, + required this.patchNumber, + required this.patchHash, + required this.platform, + required this.arch, + required this.appId, + required this.channel, + }); + + /// Converts a `Map` to a [PatchCheckRequest] + factory PatchCheckRequest.fromJson(Map json) => + _$PatchCheckRequestFromJson(json); + + /// Converts a [PatchCheckRequest] to a `Map` + Map toJson() => _$PatchCheckRequestToJson(this); + + /// The release version of the app. + final String releaseVersion; + + /// The highest patch number that the client has downloaded. + /// If provided, the server will only return patches with a higher patch + /// number. + /// If not provided, the server will provide the latest available patch. + final int? patchNumber; + + /// The current patch hash of the app. + final String? patchHash; + + /// The platform of the app. + final ReleasePlatform platform; + + /// The architecture of the app. + final String arch; + + /// The ID of the app. + final String appId; + + /// The channel of the app. + final String channel; +} diff --git a/packages/shorebird_code_push_protocol/lib/src/messages/patch_check/patch_check_request.g.dart b/packages/shorebird_code_push_protocol/lib/src/messages/patch_check/patch_check_request.g.dart new file mode 100644 index 00000000..a9e732bb --- /dev/null +++ b/packages/shorebird_code_push_protocol/lib/src/messages/patch_check/patch_check_request.g.dart @@ -0,0 +1,56 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: implicit_dynamic_parameter, require_trailing_commas, cast_nullable_to_non_nullable, lines_longer_than_80_chars, unnecessary_lambdas + +part of 'patch_check_request.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +PatchCheckRequest _$PatchCheckRequestFromJson( + Map json, +) => $checkedCreate( + 'PatchCheckRequest', + json, + ($checkedConvert) { + final val = PatchCheckRequest( + releaseVersion: $checkedConvert('release_version', (v) => v as String), + patchNumber: $checkedConvert('patch_number', (v) => (v as num?)?.toInt()), + patchHash: $checkedConvert('patch_hash', (v) => v as String?), + platform: $checkedConvert( + 'platform', + (v) => $enumDecode(_$ReleasePlatformEnumMap, v), + ), + arch: $checkedConvert('arch', (v) => v as String), + appId: $checkedConvert('app_id', (v) => v as String), + channel: $checkedConvert('channel', (v) => v as String), + ); + return val; + }, + fieldKeyMap: const { + 'releaseVersion': 'release_version', + 'patchNumber': 'patch_number', + 'patchHash': 'patch_hash', + 'appId': 'app_id', + }, +); + +Map _$PatchCheckRequestToJson(PatchCheckRequest instance) => + { + 'release_version': instance.releaseVersion, + 'patch_number': instance.patchNumber, + 'patch_hash': instance.patchHash, + 'platform': _$ReleasePlatformEnumMap[instance.platform]!, + 'arch': instance.arch, + 'app_id': instance.appId, + 'channel': instance.channel, + }; + +const _$ReleasePlatformEnumMap = { + ReleasePlatform.android: 'android', + ReleasePlatform.ios: 'ios', + ReleasePlatform.linux: 'linux', + ReleasePlatform.macos: 'macos', + ReleasePlatform.windows: 'windows', +}; diff --git a/packages/shorebird_code_push_protocol/lib/src/messages/patch_check/patch_check_response.dart b/packages/shorebird_code_push_protocol/lib/src/messages/patch_check/patch_check_response.dart new file mode 100644 index 00000000..c4dffd67 --- /dev/null +++ b/packages/shorebird_code_push_protocol/lib/src/messages/patch_check/patch_check_response.dart @@ -0,0 +1,75 @@ +import 'package:equatable/equatable.dart'; +import 'package:json_annotation/json_annotation.dart'; + +part 'patch_check_response.g.dart'; + +/// {@template patch_check_response} +/// The response body for POST /api/v1/patches/check +/// {@endtemplate} +@JsonSerializable() +class PatchCheckResponse extends Equatable { + /// {@macro patch_check_response} + const PatchCheckResponse({ + required this.patchAvailable, + this.patch, + this.rolledBackPatchNumbers, + }); + + /// Converts a `Map` to a [PatchCheckResponse] + factory PatchCheckResponse.fromJson(Map json) => + _$PatchCheckResponseFromJson(json); + + /// Converts a [PatchCheckResponse] to a `Map` + Map toJson() => _$PatchCheckResponseToJson(this); + + /// Whether a patch is available. + final bool patchAvailable; + + /// The patch metadata. + final PatchCheckMetadata? patch; + + /// The numbers of all patches that have been rolled back for the current + /// release. + final List? rolledBackPatchNumbers; + + @override + List get props => [patchAvailable, patch, rolledBackPatchNumbers]; +} + +/// {@template patch_check_metadata} +/// Patch metadata represents the contents of an update (patch) for a specific +/// platform and architecture. +/// {@endtemplate} +@JsonSerializable() +class PatchCheckMetadata extends Equatable { + /// {@macro patch_check_metadata} + const PatchCheckMetadata({ + required this.number, + required this.downloadUrl, + required this.hash, + required this.hashSignature, + }); + + /// Converts a `Map` to an [PatchCheckMetadata] + factory PatchCheckMetadata.fromJson(Map json) => + _$PatchCheckMetadataFromJson(json); + + /// Converts an [PatchCheckMetadata] to a `Map` + Map toJson() => _$PatchCheckMetadataToJson(this); + + /// The patch number associated with the artifact. + final int number; + + /// The URL of the artifact. + final String downloadUrl; + + /// The hash of the artifact. + final String hash; + + /// The signature of the [hash]. + @JsonKey(includeIfNull: false) + final String? hashSignature; + + @override + List get props => [number, downloadUrl, hash, hashSignature]; +} diff --git a/packages/shorebird_code_push_protocol/lib/src/messages/patch_check/patch_check_response.g.dart b/packages/shorebird_code_push_protocol/lib/src/messages/patch_check/patch_check_response.g.dart new file mode 100644 index 00000000..a5b07417 --- /dev/null +++ b/packages/shorebird_code_push_protocol/lib/src/messages/patch_check/patch_check_response.g.dart @@ -0,0 +1,71 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: implicit_dynamic_parameter, require_trailing_commas, cast_nullable_to_non_nullable, lines_longer_than_80_chars, unnecessary_lambdas + +part of 'patch_check_response.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +PatchCheckResponse _$PatchCheckResponseFromJson(Map json) => + $checkedCreate( + 'PatchCheckResponse', + json, + ($checkedConvert) { + final val = PatchCheckResponse( + patchAvailable: $checkedConvert('patch_available', (v) => v as bool), + patch: $checkedConvert( + 'patch', + (v) => + v == null + ? null + : PatchCheckMetadata.fromJson(v as Map), + ), + rolledBackPatchNumbers: $checkedConvert( + 'rolled_back_patch_numbers', + (v) => + (v as List?)?.map((e) => (e as num).toInt()).toList(), + ), + ); + return val; + }, + fieldKeyMap: const { + 'patchAvailable': 'patch_available', + 'rolledBackPatchNumbers': 'rolled_back_patch_numbers', + }, + ); + +Map _$PatchCheckResponseToJson(PatchCheckResponse instance) => + { + 'patch_available': instance.patchAvailable, + 'patch': instance.patch?.toJson(), + 'rolled_back_patch_numbers': instance.rolledBackPatchNumbers, + }; + +PatchCheckMetadata _$PatchCheckMetadataFromJson(Map json) => + $checkedCreate( + 'PatchCheckMetadata', + json, + ($checkedConvert) { + final val = PatchCheckMetadata( + number: $checkedConvert('number', (v) => (v as num).toInt()), + downloadUrl: $checkedConvert('download_url', (v) => v as String), + hash: $checkedConvert('hash', (v) => v as String), + hashSignature: $checkedConvert('hash_signature', (v) => v as String?), + ); + return val; + }, + fieldKeyMap: const { + 'downloadUrl': 'download_url', + 'hashSignature': 'hash_signature', + }, + ); + +Map _$PatchCheckMetadataToJson(PatchCheckMetadata instance) => + { + 'number': instance.number, + 'download_url': instance.downloadUrl, + 'hash': instance.hash, + if (instance.hashSignature case final value?) 'hash_signature': value, + }; diff --git a/packages/shorebird_code_push_protocol/test/src/messages/patch_check/patch_check_request_test.dart b/packages/shorebird_code_push_protocol/test/src/messages/patch_check/patch_check_request_test.dart new file mode 100644 index 00000000..ad0aef73 --- /dev/null +++ b/packages/shorebird_code_push_protocol/test/src/messages/patch_check/patch_check_request_test.dart @@ -0,0 +1,22 @@ +import 'package:shorebird_code_push_protocol/shorebird_code_push_protocol.dart'; +import 'package:test/test.dart'; + +void main() { + group(PatchCheckRequest, () { + test('can be (de)serialized', () { + const request = PatchCheckRequest( + releaseVersion: '1', + patchNumber: 2, + patchHash: '3', + platform: ReleasePlatform.android, + arch: 'arm64', + appId: 'app_123', + channel: 'channel_123', + ); + expect( + PatchCheckRequest.fromJson(request.toJson()).toJson(), + equals(request.toJson()), + ); + }); + }); +} diff --git a/packages/shorebird_code_push_protocol/test/src/messages/patch_check/patch_check_response_test.dart b/packages/shorebird_code_push_protocol/test/src/messages/patch_check/patch_check_response_test.dart new file mode 100644 index 00000000..56f47d51 --- /dev/null +++ b/packages/shorebird_code_push_protocol/test/src/messages/patch_check/patch_check_response_test.dart @@ -0,0 +1,129 @@ +import 'package:shorebird_code_push_protocol/shorebird_code_push_protocol.dart'; +import 'package:test/test.dart'; + +void main() { + group(PatchCheckResponse, () { + test('uses value equality', () { + expect( + // Ignoring for value quality testing. + // ignore: prefer_const_constructors + PatchCheckResponse(patchAvailable: true), + // Ignoring for value quality testing. + // ignore: prefer_const_constructors + equals(PatchCheckResponse(patchAvailable: true)), + ); + }); + + test('can be (de)serialized', () { + const response = PatchCheckResponse(patchAvailable: true); + expect( + PatchCheckResponse.fromJson(response.toJson()).toJson(), + equals(response.toJson()), + ); + }); + + test('can be serialized to json without patch metadata', () { + const response = PatchCheckResponse(patchAvailable: true); + expect(response.toJson(), { + 'patch_available': true, + 'patch': null, + 'rolled_back_patch_numbers': null, + }); + }); + + test('PatchCheckMetadata uses value equality', () { + expect( + // Ignoring for value quality testing. + // ignore: prefer_const_constructors + PatchCheckMetadata( + number: 1, + downloadUrl: 'https://download.com', + hash: '1234', + hashSignature: null, + ), + equals( + // Ignoring for value quality testing. + // ignore: prefer_const_constructors + PatchCheckMetadata( + number: 1, + downloadUrl: 'https://download.com', + hash: '1234', + hashSignature: null, + ), + ), + ); + }); + + test('PatchCheckMetadata can be (de)serialized', () { + const metadata = PatchCheckMetadata( + number: 1, + downloadUrl: 'https://download.com', + hash: '1234', + hashSignature: null, + ); + expect( + PatchCheckMetadata.fromJson(metadata.toJson()).toJson(), + equals(metadata.toJson()), + ); + }); + + test('can be serialized to json with patch metadata', () { + const response = PatchCheckResponse( + patchAvailable: true, + patch: PatchCheckMetadata( + number: 1, + downloadUrl: 'https://download.com', + hash: '1234', + hashSignature: null, + ), + ); + expect(response.toJson(), { + 'patch_available': true, + 'patch': { + 'number': 1, + 'download_url': 'https://download.com', + 'hash': '1234', + }, + 'rolled_back_patch_numbers': null, + }); + }); + + group('when there is a hash signature', () { + test('includes the signature', () { + const response = PatchCheckResponse( + patchAvailable: true, + patch: PatchCheckMetadata( + number: 1, + downloadUrl: 'https://download.com', + hash: '1234', + hashSignature: 'signature', + ), + ); + expect(response.toJson(), { + 'patch_available': true, + 'patch': { + 'number': 1, + 'download_url': 'https://download.com', + 'hash': '1234', + 'hash_signature': 'signature', + }, + 'rolled_back_patch_numbers': null, + }); + }); + }); + + group('when rolled back patch numbers are provided', () { + test('includes the rolled back patch numbers', () { + const response = PatchCheckResponse( + patchAvailable: true, + rolledBackPatchNumbers: [1, 2, 3], + ); + expect(response.toJson(), { + 'patch_available': true, + 'patch': null, + 'rolled_back_patch_numbers': [1, 2, 3], + }); + }); + }); + }); +}