feat(shorebird_code_push_protocol): add patch-adoption API surface (#3809)
This commit is contained in:
@@ -1,5 +1,22 @@
|
||||
import 'package:collection/collection.dart';
|
||||
|
||||
/// Parse a nullable string as a DateTime.
|
||||
DateTime? maybeParseDateTime(String? value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
return DateTime.parse(value);
|
||||
}
|
||||
|
||||
/// Parse a nullable RFC 3339 full-date string (`YYYY-MM-DD`) as a DateTime.
|
||||
/// Time and timezone components are zero.
|
||||
DateTime? maybeParseDate(String? value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
return DateTime.parse(value);
|
||||
}
|
||||
|
||||
/// Runs [build] to construct a `fromJson`-parsed value of type [T],
|
||||
/// converting any `TypeError` (e.g. an unexpected null or a cast
|
||||
/// failure on a required field) into a `FormatException` that names
|
||||
|
||||
@@ -29,6 +29,7 @@ export 'package:shorebird_code_push_protocol/src/messages/get_gcp_upload_speed_t
|
||||
export 'package:shorebird_code_push_protocol/src/messages/get_organization_apps/get_organization_apps_response.dart';
|
||||
export 'package:shorebird_code_push_protocol/src/messages/get_organization_users/get_organization_users_response.dart';
|
||||
export 'package:shorebird_code_push_protocol/src/messages/get_organizations_response.dart';
|
||||
export 'package:shorebird_code_push_protocol/src/messages/get_patch_adoption/get_patch_adoption_response.dart';
|
||||
export 'package:shorebird_code_push_protocol/src/messages/get_release/get_release_response.dart';
|
||||
export 'package:shorebird_code_push_protocol/src/messages/get_release_artifacts/get_release_artifacts_response.dart';
|
||||
export 'package:shorebird_code_push_protocol/src/messages/get_release_patches/get_release_patches_response.dart';
|
||||
@@ -44,12 +45,16 @@ export 'package:shorebird_code_push_protocol/src/models/app.dart';
|
||||
export 'package:shorebird_code_push_protocol/src/models/app_collaborator_role.dart';
|
||||
export 'package:shorebird_code_push_protocol/src/models/app_metadata.dart';
|
||||
export 'package:shorebird_code_push_protocol/src/models/channel.dart';
|
||||
export 'package:shorebird_code_push_protocol/src/models/get_patch_adoption_parameter3.dart';
|
||||
export 'package:shorebird_code_push_protocol/src/models/latest_release.dart';
|
||||
export 'package:shorebird_code_push_protocol/src/models/organization.dart';
|
||||
export 'package:shorebird_code_push_protocol/src/models/organization_membership.dart';
|
||||
export 'package:shorebird_code_push_protocol/src/models/organization_type.dart';
|
||||
export 'package:shorebird_code_push_protocol/src/models/organization_user.dart';
|
||||
export 'package:shorebird_code_push_protocol/src/models/patch.dart';
|
||||
export 'package:shorebird_code_push_protocol/src/models/patch_adoption_entry.dart';
|
||||
export 'package:shorebird_code_push_protocol/src/models/patch_adoption_point.dart';
|
||||
export 'package:shorebird_code_push_protocol/src/models/patch_adoption_range.dart';
|
||||
export 'package:shorebird_code_push_protocol/src/models/patch_artifact.dart';
|
||||
export 'package:shorebird_code_push_protocol/src/models/patch_check_metadata.dart';
|
||||
export 'package:shorebird_code_push_protocol/src/models/pending_release.dart';
|
||||
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:shorebird_code_push_protocol/model_helpers.dart';
|
||||
import 'package:shorebird_code_push_protocol/src/models/patch_adoption_entry.dart';
|
||||
import 'package:shorebird_code_push_protocol/src/models/patch_adoption_range.dart';
|
||||
|
||||
/// {@template get_patch_adoption_response}
|
||||
/// The response body for GET /apps/{appId}/metrics/patch-adoption. Covers
|
||||
/// exactly one release.
|
||||
/// {@endtemplate}
|
||||
@immutable
|
||||
class GetPatchAdoptionResponse {
|
||||
/// {@macro get_patch_adoption_response}
|
||||
const GetPatchAdoptionResponse({
|
||||
required this.releaseVersion,
|
||||
required this.isLatest,
|
||||
required this.granularity,
|
||||
required this.range,
|
||||
required this.asOf,
|
||||
required this.patches,
|
||||
});
|
||||
|
||||
/// Converts a `Map<String, dynamic>` to a [GetPatchAdoptionResponse].
|
||||
factory GetPatchAdoptionResponse.fromJson(Map<String, dynamic> json) {
|
||||
return parseFromJson(
|
||||
'GetPatchAdoptionResponse',
|
||||
json,
|
||||
() => GetPatchAdoptionResponse(
|
||||
releaseVersion: json['release_version'] as String,
|
||||
isLatest: json['is_latest'] as bool,
|
||||
granularity: checkedKey(json, 'granularity') as String?,
|
||||
range: PatchAdoptionRange.fromJson(
|
||||
json['range'] as Map<String, dynamic>,
|
||||
),
|
||||
asOf: DateTime.parse(json['as_of'] as String),
|
||||
patches: (json['patches'] as List)
|
||||
.map<PatchAdoptionEntry>(
|
||||
(e) => PatchAdoptionEntry.fromJson(e as Map<String, dynamic>),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Convenience to create a nullable type from a nullable json object.
|
||||
/// Useful when parsing optional fields.
|
||||
static GetPatchAdoptionResponse? maybeFromJson(Map<String, dynamic>? json) {
|
||||
if (json == null) {
|
||||
return null;
|
||||
}
|
||||
return GetPatchAdoptionResponse.fromJson(json);
|
||||
}
|
||||
|
||||
/// The release version this response is for.
|
||||
final String releaseVersion;
|
||||
|
||||
/// True when the release was resolved via the "latest release by
|
||||
/// creation date" default (no `release_version` was supplied).
|
||||
final bool isLatest;
|
||||
|
||||
/// The bucket resolution (`hour`, `day`, `week`, or `month`), or null
|
||||
/// when each patch carries a single full-window value.
|
||||
final String? granularity;
|
||||
|
||||
/// The effective (post-clamp) window the response covers.
|
||||
final PatchAdoptionRange range;
|
||||
|
||||
/// Server's UTC timestamp at the moment the response was
|
||||
/// constructed. Not a freshness indicator for the underlying
|
||||
/// data, which is refreshed by an hourly scheduled query and
|
||||
/// may lag by up to ~1 hour.
|
||||
final DateTime asOf;
|
||||
|
||||
/// One entry per patch of the release.
|
||||
final List<PatchAdoptionEntry> patches;
|
||||
|
||||
/// Converts a [GetPatchAdoptionResponse] to a `Map<String, dynamic>`.
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'release_version': releaseVersion,
|
||||
'is_latest': isLatest,
|
||||
'granularity': granularity,
|
||||
'range': range.toJson(),
|
||||
'as_of': asOf.toIso8601String(),
|
||||
'patches': patches.map((e) => e.toJson()).toList(),
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hashAll([
|
||||
releaseVersion,
|
||||
isLatest,
|
||||
granularity,
|
||||
range,
|
||||
asOf,
|
||||
listHash(patches),
|
||||
]);
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) return true;
|
||||
return other is GetPatchAdoptionResponse &&
|
||||
releaseVersion == other.releaseVersion &&
|
||||
isLatest == other.isLatest &&
|
||||
granularity == other.granularity &&
|
||||
range == other.range &&
|
||||
asOf == other.asOf &&
|
||||
listsEqual(patches, other.patches);
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
enum GetPatchAdoptionParameter3 {
|
||||
hour._('hour'),
|
||||
day._('day'),
|
||||
week._('week'),
|
||||
month._('month');
|
||||
|
||||
const GetPatchAdoptionParameter3._(this.value);
|
||||
|
||||
/// Creates a GetPatchAdoptionParameter3 from a json value.
|
||||
factory GetPatchAdoptionParameter3.fromJson(String json) {
|
||||
return GetPatchAdoptionParameter3.values.firstWhere(
|
||||
(value) => value.value == json,
|
||||
orElse: () => throw FormatException(
|
||||
'Unknown GetPatchAdoptionParameter3 value: $json',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Convenience to create a nullable type from a nullable json value.
|
||||
/// Useful when parsing optional fields.
|
||||
static GetPatchAdoptionParameter3? maybeFromJson(String? json) {
|
||||
if (json == null) {
|
||||
return null;
|
||||
}
|
||||
return GetPatchAdoptionParameter3.fromJson(json);
|
||||
}
|
||||
|
||||
/// The value of the enum. This is the exact value
|
||||
/// from the OpenAPI spec and will be used for network transport.
|
||||
final String value;
|
||||
|
||||
/// Converts the enum to its json value.
|
||||
String toJson() => value;
|
||||
|
||||
/// Returns the string form of the enum.
|
||||
@override
|
||||
String toString() => value;
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:shorebird_code_push_protocol/model_helpers.dart';
|
||||
import 'package:shorebird_code_push_protocol/src/models/patch_adoption_point.dart';
|
||||
import 'package:shorebird_code_push_protocol/src/models/release_platform.dart';
|
||||
|
||||
/// {@template patch_adoption_entry}
|
||||
/// Cumulative adoption for one patch of the release. Values are
|
||||
/// cumulative — "patch `>= patch_number`" — so they are monotonic
|
||||
/// non-increasing in `patch_number`.
|
||||
/// {@endtemplate}
|
||||
@immutable
|
||||
class PatchAdoptionEntry {
|
||||
/// {@macro patch_adoption_entry}
|
||||
const PatchAdoptionEntry({
|
||||
required this.patchNumber,
|
||||
required this.targetPlatforms,
|
||||
required this.isRolledBack,
|
||||
required this.series,
|
||||
});
|
||||
|
||||
/// Converts a `Map<String, dynamic>` to a [PatchAdoptionEntry].
|
||||
factory PatchAdoptionEntry.fromJson(Map<String, dynamic> json) {
|
||||
return parseFromJson(
|
||||
'PatchAdoptionEntry',
|
||||
json,
|
||||
() => PatchAdoptionEntry(
|
||||
patchNumber: json['patch_number'] as int,
|
||||
targetPlatforms: (json['target_platforms'] as List)
|
||||
.map<ReleasePlatform>((e) => ReleasePlatform.fromJson(e as String))
|
||||
.toList(),
|
||||
isRolledBack: json['is_rolled_back'] as bool,
|
||||
series: (json['series'] as List)
|
||||
.map<PatchAdoptionPoint>(
|
||||
(e) => PatchAdoptionPoint.fromJson(e as Map<String, dynamic>),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Convenience to create a nullable type from a nullable json object.
|
||||
/// Useful when parsing optional fields.
|
||||
static PatchAdoptionEntry? maybeFromJson(Map<String, dynamic>? json) {
|
||||
if (json == null) {
|
||||
return null;
|
||||
}
|
||||
return PatchAdoptionEntry.fromJson(json);
|
||||
}
|
||||
|
||||
/// The patch number these cumulative values are anchored at.
|
||||
final int patchNumber;
|
||||
|
||||
/// The platform(s) the patch was built for (from its artifacts). The
|
||||
/// denominator counts only devices on these platforms.
|
||||
final List<ReleasePlatform> targetPlatforms;
|
||||
|
||||
/// Whether the patch has been rolled back.
|
||||
final bool isRolledBack;
|
||||
|
||||
/// The adoption series. Exactly one point (`period: null`) when no
|
||||
/// granularity was requested; otherwise one point per bucket, ordered
|
||||
/// by `period` ascending.
|
||||
final List<PatchAdoptionPoint> series;
|
||||
|
||||
/// Converts a [PatchAdoptionEntry] to a `Map<String, dynamic>`.
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'patch_number': patchNumber,
|
||||
'target_platforms': targetPlatforms.map((e) => e.toJson()).toList(),
|
||||
'is_rolled_back': isRolledBack,
|
||||
'series': series.map((e) => e.toJson()).toList(),
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hashAll([
|
||||
patchNumber,
|
||||
listHash(targetPlatforms),
|
||||
isRolledBack,
|
||||
listHash(series),
|
||||
]);
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) return true;
|
||||
return other is PatchAdoptionEntry &&
|
||||
patchNumber == other.patchNumber &&
|
||||
listsEqual(targetPlatforms, other.targetPlatforms) &&
|
||||
isRolledBack == other.isRolledBack &&
|
||||
listsEqual(series, other.series);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:shorebird_code_push_protocol/model_helpers.dart';
|
||||
|
||||
/// {@template patch_adoption_point}
|
||||
/// One point in a patch's adoption series: the cumulative distinct
|
||||
/// devices on `patch >= patch_number` (`devices`) over the patch's target
|
||||
/// (`target`), and their ratio (`adoption_pct`), for one bucket.
|
||||
/// {@endtemplate}
|
||||
@immutable
|
||||
class PatchAdoptionPoint {
|
||||
/// {@macro patch_adoption_point}
|
||||
const PatchAdoptionPoint({
|
||||
required this.period,
|
||||
required this.devices,
|
||||
required this.target,
|
||||
required this.adoptionPct,
|
||||
});
|
||||
|
||||
/// Converts a `Map<String, dynamic>` to a [PatchAdoptionPoint].
|
||||
factory PatchAdoptionPoint.fromJson(Map<String, dynamic> json) {
|
||||
return parseFromJson(
|
||||
'PatchAdoptionPoint',
|
||||
json,
|
||||
() => PatchAdoptionPoint(
|
||||
period: maybeParseDateTime(checkedKey(json, 'period') as String?),
|
||||
devices: json['devices'] as int,
|
||||
target: json['target'] as int,
|
||||
adoptionPct: (json['adoption_pct'] as num).toDouble(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Convenience to create a nullable type from a nullable json object.
|
||||
/// Useful when parsing optional fields.
|
||||
static PatchAdoptionPoint? maybeFromJson(Map<String, dynamic>? json) {
|
||||
if (json == null) {
|
||||
return null;
|
||||
}
|
||||
return PatchAdoptionPoint.fromJson(json);
|
||||
}
|
||||
|
||||
/// The bucket start (UTC), or null when the response is a single
|
||||
/// full-window value (no granularity requested).
|
||||
final DateTime? period;
|
||||
|
||||
/// Distinct devices running patch `>= patch_number` on the patch's
|
||||
/// target platform(s) within this bucket (an HLL count).
|
||||
final int devices;
|
||||
|
||||
/// Distinct devices on the release whose platform the patch targets —
|
||||
/// the patch's reachable denominator — within this bucket.
|
||||
final int target;
|
||||
|
||||
/// `devices / target`, in [0.0, 1.0] (0 when `target` is 0). Server
|
||||
/// is the source of truth; clients should render this value rather
|
||||
/// than recomputing it.
|
||||
final double adoptionPct;
|
||||
|
||||
/// Converts a [PatchAdoptionPoint] to a `Map<String, dynamic>`.
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'period': period?.toIso8601String(),
|
||||
'devices': devices,
|
||||
'target': target,
|
||||
'adoption_pct': adoptionPct,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hashAll([
|
||||
period,
|
||||
devices,
|
||||
target,
|
||||
adoptionPct,
|
||||
]);
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) return true;
|
||||
return other is PatchAdoptionPoint &&
|
||||
period == other.period &&
|
||||
devices == other.devices &&
|
||||
target == other.target &&
|
||||
adoptionPct == other.adoptionPct;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:shorebird_code_push_protocol/model_helpers.dart';
|
||||
|
||||
/// {@template patch_adoption_range}
|
||||
/// The effective (post-clamp) window the response covers.
|
||||
/// {@endtemplate}
|
||||
@immutable
|
||||
class PatchAdoptionRange {
|
||||
/// {@macro patch_adoption_range}
|
||||
const PatchAdoptionRange({
|
||||
required this.start,
|
||||
required this.end,
|
||||
});
|
||||
|
||||
/// Converts a `Map<String, dynamic>` to a [PatchAdoptionRange].
|
||||
factory PatchAdoptionRange.fromJson(Map<String, dynamic> json) {
|
||||
return parseFromJson(
|
||||
'PatchAdoptionRange',
|
||||
json,
|
||||
() => PatchAdoptionRange(
|
||||
start: DateTime.parse(json['start'] as String),
|
||||
end: DateTime.parse(json['end'] as String),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Convenience to create a nullable type from a nullable json object.
|
||||
/// Useful when parsing optional fields.
|
||||
static PatchAdoptionRange? maybeFromJson(Map<String, dynamic>? json) {
|
||||
if (json == null) {
|
||||
return null;
|
||||
}
|
||||
return PatchAdoptionRange.fromJson(json);
|
||||
}
|
||||
|
||||
/// Window start (UTC, inclusive).
|
||||
final DateTime start;
|
||||
|
||||
/// Window end (UTC, exclusive).
|
||||
final DateTime end;
|
||||
|
||||
/// Converts a [PatchAdoptionRange] to a `Map<String, dynamic>`.
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'start': start.toIso8601String(),
|
||||
'end': end.toIso8601String(),
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hashAll([
|
||||
start,
|
||||
end,
|
||||
]);
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) return true;
|
||||
return other is PatchAdoptionRange &&
|
||||
start == other.start &&
|
||||
end == other.end;
|
||||
}
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
// GENERATED — do not hand-edit.
|
||||
import 'package:shorebird_code_push_protocol/shorebird_code_push_protocol.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
group('GetPatchAdoptionResponse', () {
|
||||
test('round-trips via maybeFromJson/toJson', () {
|
||||
final instance = GetPatchAdoptionResponse(
|
||||
releaseVersion: 'example',
|
||||
isLatest: false,
|
||||
granularity: 'example',
|
||||
range: PatchAdoptionRange(
|
||||
start: DateTime.utc(2024),
|
||||
end: DateTime.utc(2024),
|
||||
),
|
||||
asOf: DateTime.utc(2024),
|
||||
patches: <PatchAdoptionEntry>[
|
||||
PatchAdoptionEntry(
|
||||
patchNumber: 0,
|
||||
targetPlatforms: <ReleasePlatform>[ReleasePlatform.values.first],
|
||||
isRolledBack: false,
|
||||
series: <PatchAdoptionPoint>[
|
||||
PatchAdoptionPoint(
|
||||
period: DateTime.utc(2024),
|
||||
devices: 0,
|
||||
target: 0,
|
||||
adoptionPct: 0,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
final parsed = GetPatchAdoptionResponse.maybeFromJson(instance.toJson())!;
|
||||
expect(parsed, equals(instance));
|
||||
expect(parsed.hashCode, equals(instance.hashCode));
|
||||
});
|
||||
|
||||
test('maybeFromJson returns null on null input', () {
|
||||
expect(GetPatchAdoptionResponse.maybeFromJson(null), isNull);
|
||||
});
|
||||
|
||||
test('maybeFromJson throws FormatException on invalid input', () {
|
||||
expect(
|
||||
() => GetPatchAdoptionResponse.maybeFromJson(<String, dynamic>{}),
|
||||
throwsFormatException,
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
// GENERATED — do not hand-edit.
|
||||
import 'package:shorebird_code_push_protocol/shorebird_code_push_protocol.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
group('GetPatchAdoptionParameter3', () {
|
||||
test('round-trips via maybeFromJson/toJson', () {
|
||||
final instance = GetPatchAdoptionParameter3.values.first;
|
||||
final parsed = GetPatchAdoptionParameter3.maybeFromJson(
|
||||
instance.toJson(),
|
||||
)!;
|
||||
expect(parsed, equals(instance));
|
||||
expect(parsed.hashCode, equals(instance.hashCode));
|
||||
});
|
||||
|
||||
test('maybeFromJson returns null on null input', () {
|
||||
expect(GetPatchAdoptionParameter3.maybeFromJson(null), isNull);
|
||||
});
|
||||
|
||||
test('maybeFromJson throws FormatException on invalid input', () {
|
||||
expect(
|
||||
() =>
|
||||
GetPatchAdoptionParameter3.maybeFromJson('__invalid_enum_value__'),
|
||||
throwsFormatException,
|
||||
);
|
||||
});
|
||||
|
||||
test('toString matches toJson for every value', () {
|
||||
for (final value in GetPatchAdoptionParameter3.values) {
|
||||
expect(value.toString(), equals(value.toJson()));
|
||||
}
|
||||
});
|
||||
|
||||
test('fromJson round-trips every value', () {
|
||||
for (final value in GetPatchAdoptionParameter3.values) {
|
||||
expect(
|
||||
GetPatchAdoptionParameter3.fromJson(value.toJson()),
|
||||
equals(value),
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
// GENERATED — do not hand-edit.
|
||||
import 'package:shorebird_code_push_protocol/shorebird_code_push_protocol.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
group('PatchAdoptionEntry', () {
|
||||
test('round-trips via maybeFromJson/toJson', () {
|
||||
final instance = PatchAdoptionEntry(
|
||||
patchNumber: 0,
|
||||
targetPlatforms: <ReleasePlatform>[ReleasePlatform.values.first],
|
||||
isRolledBack: false,
|
||||
series: <PatchAdoptionPoint>[
|
||||
PatchAdoptionPoint(
|
||||
period: DateTime.utc(2024),
|
||||
devices: 0,
|
||||
target: 0,
|
||||
adoptionPct: 0,
|
||||
),
|
||||
],
|
||||
);
|
||||
final parsed = PatchAdoptionEntry.maybeFromJson(instance.toJson())!;
|
||||
expect(parsed, equals(instance));
|
||||
expect(parsed.hashCode, equals(instance.hashCode));
|
||||
});
|
||||
|
||||
test('maybeFromJson returns null on null input', () {
|
||||
expect(PatchAdoptionEntry.maybeFromJson(null), isNull);
|
||||
});
|
||||
|
||||
test('maybeFromJson throws FormatException on invalid input', () {
|
||||
expect(
|
||||
() => PatchAdoptionEntry.maybeFromJson(<String, dynamic>{}),
|
||||
throwsFormatException,
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
// GENERATED — do not hand-edit.
|
||||
import 'package:shorebird_code_push_protocol/shorebird_code_push_protocol.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
group('PatchAdoptionPoint', () {
|
||||
test('round-trips via maybeFromJson/toJson', () {
|
||||
final instance = PatchAdoptionPoint(
|
||||
period: DateTime.utc(2024),
|
||||
devices: 0,
|
||||
target: 0,
|
||||
adoptionPct: 0,
|
||||
);
|
||||
final parsed = PatchAdoptionPoint.maybeFromJson(instance.toJson())!;
|
||||
expect(parsed, equals(instance));
|
||||
expect(parsed.hashCode, equals(instance.hashCode));
|
||||
});
|
||||
|
||||
test('maybeFromJson returns null on null input', () {
|
||||
expect(PatchAdoptionPoint.maybeFromJson(null), isNull);
|
||||
});
|
||||
|
||||
test('maybeFromJson throws FormatException on invalid input', () {
|
||||
expect(
|
||||
() => PatchAdoptionPoint.maybeFromJson(<String, dynamic>{}),
|
||||
throwsFormatException,
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
// GENERATED — do not hand-edit.
|
||||
import 'package:shorebird_code_push_protocol/shorebird_code_push_protocol.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
group('PatchAdoptionRange', () {
|
||||
test('round-trips via maybeFromJson/toJson', () {
|
||||
final instance = PatchAdoptionRange(
|
||||
start: DateTime.utc(2024),
|
||||
end: DateTime.utc(2024),
|
||||
);
|
||||
final parsed = PatchAdoptionRange.maybeFromJson(instance.toJson())!;
|
||||
expect(parsed, equals(instance));
|
||||
expect(parsed.hashCode, equals(instance.hashCode));
|
||||
});
|
||||
|
||||
test('maybeFromJson returns null on null input', () {
|
||||
expect(PatchAdoptionRange.maybeFromJson(null), isNull);
|
||||
});
|
||||
|
||||
test('maybeFromJson throws FormatException on invalid input', () {
|
||||
expect(
|
||||
() => PatchAdoptionRange.maybeFromJson(<String, dynamic>{}),
|
||||
throwsFormatException,
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user