From 1ad5bd09392e855b96ce111c9af5bbd86a809a9e Mon Sep 17 00:00:00 2001 From: Mac Date: Fri, 12 Jun 2026 11:04:55 -0600 Subject: [PATCH] feat(shorebird_code_push_protocol): add unique-users metric API surface (#3814) --- .../lib/shorebird_code_push_protocol.dart | 6 + .../get_unique_users_response.dart | 114 ++++++++++++++++++ .../models/get_unique_users_parameter2.dart | 38 ++++++ .../models/get_unique_users_parameter3.dart | 35 ++++++ .../models/unique_users_breakdown_entry.dart | 89 ++++++++++++++ .../lib/src/models/unique_users_range.dart | 63 ++++++++++ .../unique_users_time_series_entry.dart | 64 ++++++++++ .../get_unique_users_response_test.dart | 33 +++++ .../get_unique_users_parameter2_test.dart | 40 ++++++ .../get_unique_users_parameter3_test.dart | 40 ++++++ .../unique_users_breakdown_entry_test.dart | 31 +++++ .../models/unique_users_range_test.dart | 28 +++++ .../unique_users_time_series_entry_test.dart | 30 +++++ 13 files changed, 611 insertions(+) create mode 100644 packages/shorebird_code_push_protocol/lib/src/messages/get_unique_users/get_unique_users_response.dart create mode 100644 packages/shorebird_code_push_protocol/lib/src/models/get_unique_users_parameter2.dart create mode 100644 packages/shorebird_code_push_protocol/lib/src/models/get_unique_users_parameter3.dart create mode 100644 packages/shorebird_code_push_protocol/lib/src/models/unique_users_breakdown_entry.dart create mode 100644 packages/shorebird_code_push_protocol/lib/src/models/unique_users_range.dart create mode 100644 packages/shorebird_code_push_protocol/lib/src/models/unique_users_time_series_entry.dart create mode 100644 packages/shorebird_code_push_protocol/test/generated/messages/get_unique_users/get_unique_users_response_test.dart create mode 100644 packages/shorebird_code_push_protocol/test/generated/models/get_unique_users_parameter2_test.dart create mode 100644 packages/shorebird_code_push_protocol/test/generated/models/get_unique_users_parameter3_test.dart create mode 100644 packages/shorebird_code_push_protocol/test/generated/models/unique_users_breakdown_entry_test.dart create mode 100644 packages/shorebird_code_push_protocol/test/generated/models/unique_users_range_test.dart create mode 100644 packages/shorebird_code_push_protocol/test/generated/models/unique_users_time_series_entry_test.dart diff --git a/packages/shorebird_code_push_protocol/lib/shorebird_code_push_protocol.dart b/packages/shorebird_code_push_protocol/lib/shorebird_code_push_protocol.dart index 3f20f644..e15975bb 100644 --- a/packages/shorebird_code_push_protocol/lib/shorebird_code_push_protocol.dart +++ b/packages/shorebird_code_push_protocol/lib/shorebird_code_push_protocol.dart @@ -35,6 +35,7 @@ export 'package:shorebird_code_push_protocol/src/messages/get_release/get_releas 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'; export 'package:shorebird_code_push_protocol/src/messages/get_releases/get_releases_response.dart'; +export 'package:shorebird_code_push_protocol/src/messages/get_unique_users/get_unique_users_response.dart'; export 'package:shorebird_code_push_protocol/src/messages/get_version_distribution/get_version_distribution_response.dart'; export 'package:shorebird_code_push_protocol/src/messages/patch_check/patch_check_request.dart'; export 'package:shorebird_code_push_protocol/src/messages/patch_check/patch_check_response.dart'; @@ -48,6 +49,8 @@ export 'package:shorebird_code_push_protocol/src/models/app_collaborator_role.da 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/get_unique_users_parameter2.dart'; +export 'package:shorebird_code_push_protocol/src/models/get_unique_users_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'; @@ -69,6 +72,9 @@ export 'package:shorebird_code_push_protocol/src/models/release_patch.dart'; export 'package:shorebird_code_push_protocol/src/models/release_platform.dart'; export 'package:shorebird_code_push_protocol/src/models/release_status.dart'; export 'package:shorebird_code_push_protocol/src/models/role.dart'; +export 'package:shorebird_code_push_protocol/src/models/unique_users_breakdown_entry.dart'; +export 'package:shorebird_code_push_protocol/src/models/unique_users_range.dart'; +export 'package:shorebird_code_push_protocol/src/models/unique_users_time_series_entry.dart'; export 'package:shorebird_code_push_protocol/src/models/version_distribution_entry.dart'; /// Parsed JSON data. diff --git a/packages/shorebird_code_push_protocol/lib/src/messages/get_unique_users/get_unique_users_response.dart b/packages/shorebird_code_push_protocol/lib/src/messages/get_unique_users/get_unique_users_response.dart new file mode 100644 index 00000000..07eccdb4 --- /dev/null +++ b/packages/shorebird_code_push_protocol/lib/src/messages/get_unique_users/get_unique_users_response.dart @@ -0,0 +1,114 @@ +import 'package:meta/meta.dart'; +import 'package:shorebird_code_push_protocol/model_helpers.dart'; +import 'package:shorebird_code_push_protocol/src/models/unique_users_breakdown_entry.dart'; +import 'package:shorebird_code_push_protocol/src/models/unique_users_range.dart'; +import 'package:shorebird_code_push_protocol/src/models/unique_users_time_series_entry.dart'; + +/// {@template get_unique_users_response} +/// The response body for GET /apps/{appId}/metrics/unique-users. +/// {@endtemplate} +@immutable +class GetUniqueUsersResponse { + /// {@macro get_unique_users_response} + const GetUniqueUsersResponse({ + required this.uniqueUsers, + required this.granularity, + required this.range, + required this.asOf, + this.timeSeries, + this.breakdown, + }); + + /// Converts a `Map` to a [GetUniqueUsersResponse]. + factory GetUniqueUsersResponse.fromJson(Map json) { + return parseFromJson( + 'GetUniqueUsersResponse', + json, + () => GetUniqueUsersResponse( + uniqueUsers: json['unique_users'] as int, + granularity: checkedKey(json, 'granularity') as String?, + range: UniqueUsersRange.fromJson(json['range'] as Map), + asOf: DateTime.parse(json['as_of'] as String), + timeSeries: (json['time_series'] as List?) + ?.map( + (e) => UniqueUsersTimeSeriesEntry.fromJson( + e as Map, + ), + ) + .toList(), + breakdown: (json['breakdown'] as List?) + ?.map( + (e) => + UniqueUsersBreakdownEntry.fromJson(e as Map), + ) + .toList(), + ), + ); + } + + /// Convenience to create a nullable type from a nullable json object. + /// Useful when parsing optional fields. + static GetUniqueUsersResponse? maybeFromJson(Map? json) { + if (json == null) { + return null; + } + return GetUniqueUsersResponse.fromJson(json); + } + + /// Distinct active devices over the whole window (an HLL count). + final int uniqueUsers; + + /// The time-series bucket resolution (`hour`, `day`, `week`, or + /// `month`), or null when no time series was requested. + final String? granularity; + + /// The window the unique-users response covers. + final UniqueUsersRange 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; + + /// The top-level time series, present only when a `granularity` was + /// requested; otherwise null. + final List? timeSeries; + + /// Per-group unique users, present only when a `group_by` was + /// requested; otherwise null. + final List? breakdown; + + /// Converts a [GetUniqueUsersResponse] to a `Map`. + Map toJson() { + return { + 'unique_users': uniqueUsers, + 'granularity': granularity, + 'range': range.toJson(), + 'as_of': asOf.toIso8601String(), + 'time_series': timeSeries?.map((e) => e.toJson()).toList(), + 'breakdown': breakdown?.map((e) => e.toJson()).toList(), + }; + } + + @override + int get hashCode => Object.hashAll([ + uniqueUsers, + granularity, + range, + asOf, + listHash(timeSeries), + listHash(breakdown), + ]); + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + return other is GetUniqueUsersResponse && + uniqueUsers == other.uniqueUsers && + granularity == other.granularity && + range == other.range && + asOf == other.asOf && + listsEqual(timeSeries, other.timeSeries) && + listsEqual(breakdown, other.breakdown); + } +} diff --git a/packages/shorebird_code_push_protocol/lib/src/models/get_unique_users_parameter2.dart b/packages/shorebird_code_push_protocol/lib/src/models/get_unique_users_parameter2.dart new file mode 100644 index 00000000..5676df55 --- /dev/null +++ b/packages/shorebird_code_push_protocol/lib/src/models/get_unique_users_parameter2.dart @@ -0,0 +1,38 @@ +enum GetUniqueUsersParameter2 { + hour._('hour'), + day._('day'), + week._('week'), + month._('month'); + + const GetUniqueUsersParameter2._(this.value); + + /// Creates a GetUniqueUsersParameter2 from a json value. + factory GetUniqueUsersParameter2.fromJson(String json) { + return GetUniqueUsersParameter2.values.firstWhere( + (value) => value.value == json, + orElse: () => throw FormatException( + 'Unknown GetUniqueUsersParameter2 value: $json', + ), + ); + } + + /// Convenience to create a nullable type from a nullable json value. + /// Useful when parsing optional fields. + static GetUniqueUsersParameter2? maybeFromJson(String? json) { + if (json == null) { + return null; + } + return GetUniqueUsersParameter2.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; +} diff --git a/packages/shorebird_code_push_protocol/lib/src/models/get_unique_users_parameter3.dart b/packages/shorebird_code_push_protocol/lib/src/models/get_unique_users_parameter3.dart new file mode 100644 index 00000000..97daea87 --- /dev/null +++ b/packages/shorebird_code_push_protocol/lib/src/models/get_unique_users_parameter3.dart @@ -0,0 +1,35 @@ +enum GetUniqueUsersParameter3 { + platform._('platform'); + + const GetUniqueUsersParameter3._(this.value); + + /// Creates a GetUniqueUsersParameter3 from a json value. + factory GetUniqueUsersParameter3.fromJson(String json) { + return GetUniqueUsersParameter3.values.firstWhere( + (value) => value.value == json, + orElse: () => throw FormatException( + 'Unknown GetUniqueUsersParameter3 value: $json', + ), + ); + } + + /// Convenience to create a nullable type from a nullable json value. + /// Useful when parsing optional fields. + static GetUniqueUsersParameter3? maybeFromJson(String? json) { + if (json == null) { + return null; + } + return GetUniqueUsersParameter3.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; +} diff --git a/packages/shorebird_code_push_protocol/lib/src/models/unique_users_breakdown_entry.dart b/packages/shorebird_code_push_protocol/lib/src/models/unique_users_breakdown_entry.dart new file mode 100644 index 00000000..bdeffa4e --- /dev/null +++ b/packages/shorebird_code_push_protocol/lib/src/models/unique_users_breakdown_entry.dart @@ -0,0 +1,89 @@ +import 'package:meta/meta.dart'; +import 'package:shorebird_code_push_protocol/model_helpers.dart'; +import 'package:shorebird_code_push_protocol/src/models/unique_users_time_series_entry.dart'; + +/// {@template unique_users_breakdown_entry} +/// Unique users for one value of the `group_by` dimension (e.g. one +/// platform), optionally with its own time series. +/// {@endtemplate} +@immutable +class UniqueUsersBreakdownEntry { + /// {@macro unique_users_breakdown_entry} + const UniqueUsersBreakdownEntry({ + required this.groupBy, + required this.groupValue, + required this.uniqueUsers, + this.timeSeries, + }); + + /// Converts a `Map` to a [UniqueUsersBreakdownEntry]. + factory UniqueUsersBreakdownEntry.fromJson(Map json) { + return parseFromJson( + 'UniqueUsersBreakdownEntry', + json, + () => UniqueUsersBreakdownEntry( + groupBy: json['group_by'] as String, + groupValue: json['group_value'] as String, + uniqueUsers: json['unique_users'] as int, + timeSeries: (json['time_series'] as List?) + ?.map( + (e) => UniqueUsersTimeSeriesEntry.fromJson( + e as Map, + ), + ) + .toList(), + ), + ); + } + + /// Convenience to create a nullable type from a nullable json object. + /// Useful when parsing optional fields. + static UniqueUsersBreakdownEntry? maybeFromJson(Map? json) { + if (json == null) { + return null; + } + return UniqueUsersBreakdownEntry.fromJson(json); + } + + /// The dimension this entry breaks down by (e.g. "platform"). + final String groupBy; + + /// The value within `group_by` (e.g. "android"). + final String groupValue; + + /// Distinct active devices for this group over the window (an HLL + /// count). + final int uniqueUsers; + + /// Per-bucket series for this group, present only when a `granularity` + /// was requested; otherwise null. + final List? timeSeries; + + /// Converts a [UniqueUsersBreakdownEntry] to a `Map`. + Map toJson() { + return { + 'group_by': groupBy, + 'group_value': groupValue, + 'unique_users': uniqueUsers, + 'time_series': timeSeries?.map((e) => e.toJson()).toList(), + }; + } + + @override + int get hashCode => Object.hashAll([ + groupBy, + groupValue, + uniqueUsers, + listHash(timeSeries), + ]); + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + return other is UniqueUsersBreakdownEntry && + groupBy == other.groupBy && + groupValue == other.groupValue && + uniqueUsers == other.uniqueUsers && + listsEqual(timeSeries, other.timeSeries); + } +} diff --git a/packages/shorebird_code_push_protocol/lib/src/models/unique_users_range.dart b/packages/shorebird_code_push_protocol/lib/src/models/unique_users_range.dart new file mode 100644 index 00000000..e986bd8d --- /dev/null +++ b/packages/shorebird_code_push_protocol/lib/src/models/unique_users_range.dart @@ -0,0 +1,63 @@ +import 'package:meta/meta.dart'; +import 'package:shorebird_code_push_protocol/model_helpers.dart'; + +/// {@template unique_users_range} +/// The window the unique-users response covers. +/// {@endtemplate} +@immutable +class UniqueUsersRange { + /// {@macro unique_users_range} + const UniqueUsersRange({ + required this.start, + required this.end, + }); + + /// Converts a `Map` to a [UniqueUsersRange]. + factory UniqueUsersRange.fromJson(Map json) { + return parseFromJson( + 'UniqueUsersRange', + json, + () => UniqueUsersRange( + 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 UniqueUsersRange? maybeFromJson(Map? json) { + if (json == null) { + return null; + } + return UniqueUsersRange.fromJson(json); + } + + /// Window start (UTC, inclusive). + final DateTime start; + + /// Window end (UTC, exclusive). + final DateTime end; + + /// Converts a [UniqueUsersRange] to a `Map`. + Map 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 UniqueUsersRange && + start == other.start && + end == other.end; + } +} diff --git a/packages/shorebird_code_push_protocol/lib/src/models/unique_users_time_series_entry.dart b/packages/shorebird_code_push_protocol/lib/src/models/unique_users_time_series_entry.dart new file mode 100644 index 00000000..04c6167d --- /dev/null +++ b/packages/shorebird_code_push_protocol/lib/src/models/unique_users_time_series_entry.dart @@ -0,0 +1,64 @@ +import 'package:meta/meta.dart'; +import 'package:shorebird_code_push_protocol/model_helpers.dart'; + +/// {@template unique_users_time_series_entry} +/// One bucket of a unique-users time series: the HLL count of distinct +/// active devices in the bucket starting at `period`. +/// {@endtemplate} +@immutable +class UniqueUsersTimeSeriesEntry { + /// {@macro unique_users_time_series_entry} + const UniqueUsersTimeSeriesEntry({ + required this.period, + required this.uniqueUsers, + }); + + /// Converts a `Map` to a [UniqueUsersTimeSeriesEntry]. + factory UniqueUsersTimeSeriesEntry.fromJson(Map json) { + return parseFromJson( + 'UniqueUsersTimeSeriesEntry', + json, + () => UniqueUsersTimeSeriesEntry( + period: DateTime.parse(json['period'] as String), + uniqueUsers: json['unique_users'] as int, + ), + ); + } + + /// Convenience to create a nullable type from a nullable json object. + /// Useful when parsing optional fields. + static UniqueUsersTimeSeriesEntry? maybeFromJson(Map? json) { + if (json == null) { + return null; + } + return UniqueUsersTimeSeriesEntry.fromJson(json); + } + + /// The bucket start (UTC). + final DateTime period; + + /// Distinct active devices in this bucket (an HLL count). + final int uniqueUsers; + + /// Converts a [UniqueUsersTimeSeriesEntry] to a `Map`. + Map toJson() { + return { + 'period': period.toIso8601String(), + 'unique_users': uniqueUsers, + }; + } + + @override + int get hashCode => Object.hashAll([ + period, + uniqueUsers, + ]); + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + return other is UniqueUsersTimeSeriesEntry && + period == other.period && + uniqueUsers == other.uniqueUsers; + } +} diff --git a/packages/shorebird_code_push_protocol/test/generated/messages/get_unique_users/get_unique_users_response_test.dart b/packages/shorebird_code_push_protocol/test/generated/messages/get_unique_users/get_unique_users_response_test.dart new file mode 100644 index 00000000..ccd26b09 --- /dev/null +++ b/packages/shorebird_code_push_protocol/test/generated/messages/get_unique_users/get_unique_users_response_test.dart @@ -0,0 +1,33 @@ +// GENERATED — do not hand-edit. +import 'package:shorebird_code_push_protocol/shorebird_code_push_protocol.dart'; +import 'package:test/test.dart'; + +void main() { + group('GetUniqueUsersResponse', () { + test('round-trips via maybeFromJson/toJson', () { + final instance = GetUniqueUsersResponse( + uniqueUsers: 0, + granularity: 'example', + range: UniqueUsersRange( + start: DateTime.utc(2024), + end: DateTime.utc(2024), + ), + asOf: DateTime.utc(2024), + ); + final parsed = GetUniqueUsersResponse.maybeFromJson(instance.toJson()); + expect(parsed, equals(instance)); + expect(parsed.hashCode, equals(instance.hashCode)); + }); + + test('maybeFromJson returns null on null input', () { + expect(GetUniqueUsersResponse.maybeFromJson(null), isNull); + }); + + test('maybeFromJson throws FormatException on invalid input', () { + expect( + () => GetUniqueUsersResponse.maybeFromJson({}), + throwsFormatException, + ); + }); + }); +} diff --git a/packages/shorebird_code_push_protocol/test/generated/models/get_unique_users_parameter2_test.dart b/packages/shorebird_code_push_protocol/test/generated/models/get_unique_users_parameter2_test.dart new file mode 100644 index 00000000..62eae97f --- /dev/null +++ b/packages/shorebird_code_push_protocol/test/generated/models/get_unique_users_parameter2_test.dart @@ -0,0 +1,40 @@ +// GENERATED — do not hand-edit. +import 'package:shorebird_code_push_protocol/shorebird_code_push_protocol.dart'; +import 'package:test/test.dart'; + +void main() { + group('GetUniqueUsersParameter2', () { + test('round-trips via maybeFromJson/toJson', () { + final instance = GetUniqueUsersParameter2.values.first; + final parsed = GetUniqueUsersParameter2.maybeFromJson(instance.toJson()); + expect(parsed, equals(instance)); + expect(parsed.hashCode, equals(instance.hashCode)); + }); + + test('maybeFromJson returns null on null input', () { + expect(GetUniqueUsersParameter2.maybeFromJson(null), isNull); + }); + + test('maybeFromJson throws FormatException on invalid input', () { + expect( + () => GetUniqueUsersParameter2.maybeFromJson('__invalid_enum_value__'), + throwsFormatException, + ); + }); + + test('toString matches toJson for every value', () { + for (final value in GetUniqueUsersParameter2.values) { + expect(value.toString(), equals(value.toJson())); + } + }); + + test('fromJson round-trips every value', () { + for (final value in GetUniqueUsersParameter2.values) { + expect( + GetUniqueUsersParameter2.fromJson(value.toJson()), + equals(value), + ); + } + }); + }); +} diff --git a/packages/shorebird_code_push_protocol/test/generated/models/get_unique_users_parameter3_test.dart b/packages/shorebird_code_push_protocol/test/generated/models/get_unique_users_parameter3_test.dart new file mode 100644 index 00000000..c98492ff --- /dev/null +++ b/packages/shorebird_code_push_protocol/test/generated/models/get_unique_users_parameter3_test.dart @@ -0,0 +1,40 @@ +// GENERATED — do not hand-edit. +import 'package:shorebird_code_push_protocol/shorebird_code_push_protocol.dart'; +import 'package:test/test.dart'; + +void main() { + group('GetUniqueUsersParameter3', () { + test('round-trips via maybeFromJson/toJson', () { + final instance = GetUniqueUsersParameter3.values.first; + final parsed = GetUniqueUsersParameter3.maybeFromJson(instance.toJson()); + expect(parsed, equals(instance)); + expect(parsed.hashCode, equals(instance.hashCode)); + }); + + test('maybeFromJson returns null on null input', () { + expect(GetUniqueUsersParameter3.maybeFromJson(null), isNull); + }); + + test('maybeFromJson throws FormatException on invalid input', () { + expect( + () => GetUniqueUsersParameter3.maybeFromJson('__invalid_enum_value__'), + throwsFormatException, + ); + }); + + test('toString matches toJson for every value', () { + for (final value in GetUniqueUsersParameter3.values) { + expect(value.toString(), equals(value.toJson())); + } + }); + + test('fromJson round-trips every value', () { + for (final value in GetUniqueUsersParameter3.values) { + expect( + GetUniqueUsersParameter3.fromJson(value.toJson()), + equals(value), + ); + } + }); + }); +} diff --git a/packages/shorebird_code_push_protocol/test/generated/models/unique_users_breakdown_entry_test.dart b/packages/shorebird_code_push_protocol/test/generated/models/unique_users_breakdown_entry_test.dart new file mode 100644 index 00000000..fa23400f --- /dev/null +++ b/packages/shorebird_code_push_protocol/test/generated/models/unique_users_breakdown_entry_test.dart @@ -0,0 +1,31 @@ +// GENERATED — do not hand-edit. +import 'package:shorebird_code_push_protocol/shorebird_code_push_protocol.dart'; +import 'package:test/test.dart'; + +void main() { + group('UniqueUsersBreakdownEntry', () { + test('round-trips via maybeFromJson/toJson', () { + final instance = UniqueUsersBreakdownEntry( + groupBy: 'example', + groupValue: 'example', + uniqueUsers: 0, + ); + final parsed = UniqueUsersBreakdownEntry.maybeFromJson( + instance.toJson(), + ); + expect(parsed, equals(instance)); + expect(parsed.hashCode, equals(instance.hashCode)); + }); + + test('maybeFromJson returns null on null input', () { + expect(UniqueUsersBreakdownEntry.maybeFromJson(null), isNull); + }); + + test('maybeFromJson throws FormatException on invalid input', () { + expect( + () => UniqueUsersBreakdownEntry.maybeFromJson({}), + throwsFormatException, + ); + }); + }); +} diff --git a/packages/shorebird_code_push_protocol/test/generated/models/unique_users_range_test.dart b/packages/shorebird_code_push_protocol/test/generated/models/unique_users_range_test.dart new file mode 100644 index 00000000..afe37f73 --- /dev/null +++ b/packages/shorebird_code_push_protocol/test/generated/models/unique_users_range_test.dart @@ -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('UniqueUsersRange', () { + test('round-trips via maybeFromJson/toJson', () { + final instance = UniqueUsersRange( + start: DateTime.utc(2024), + end: DateTime.utc(2024), + ); + final parsed = UniqueUsersRange.maybeFromJson(instance.toJson()); + expect(parsed, equals(instance)); + expect(parsed.hashCode, equals(instance.hashCode)); + }); + + test('maybeFromJson returns null on null input', () { + expect(UniqueUsersRange.maybeFromJson(null), isNull); + }); + + test('maybeFromJson throws FormatException on invalid input', () { + expect( + () => UniqueUsersRange.maybeFromJson({}), + throwsFormatException, + ); + }); + }); +} diff --git a/packages/shorebird_code_push_protocol/test/generated/models/unique_users_time_series_entry_test.dart b/packages/shorebird_code_push_protocol/test/generated/models/unique_users_time_series_entry_test.dart new file mode 100644 index 00000000..11316624 --- /dev/null +++ b/packages/shorebird_code_push_protocol/test/generated/models/unique_users_time_series_entry_test.dart @@ -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('UniqueUsersTimeSeriesEntry', () { + test('round-trips via maybeFromJson/toJson', () { + final instance = UniqueUsersTimeSeriesEntry( + period: DateTime.utc(2024), + uniqueUsers: 0, + ); + final parsed = UniqueUsersTimeSeriesEntry.maybeFromJson( + instance.toJson(), + ); + expect(parsed, equals(instance)); + expect(parsed.hashCode, equals(instance.hashCode)); + }); + + test('maybeFromJson returns null on null input', () { + expect(UniqueUsersTimeSeriesEntry.maybeFromJson(null), isNull); + }); + + test('maybeFromJson throws FormatException on invalid input', () { + expect( + () => UniqueUsersTimeSeriesEntry.maybeFromJson({}), + throwsFormatException, + ); + }); + }); +}