summaryrefslogtreecommitdiff
path: root/player-android/test
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-05-20 07:53:14 +0300
committerPaul Buetow <paul@buetow.org>2026-05-20 07:53:14 +0300
commitf2f596dc70756402fd27edea69cfd399dd39b183 (patch)
tree42d60940b0e4dad500dccf956e597afde659dd66 /player-android/test
parent26d3dc4e031cf195638d2483bcfcdf45fd216502 (diff)
Guard dateTimeFromJson against malformed date strings (c9)
Wrap DateTime.parse in try/catch so a malformed or non-ISO-8601 date string from the server degrades to null instead of throwing a FormatException that crashes model deserialization across Media, MediaSet, User, PodcastFeed, PodcastEpisode, PlaybackHint, Share, and Note. Add player-android/test/json_helpers_test.dart with 11 tests covering null, empty, non-string, valid ISO, and malformed inputs (regression guard for the original crash). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Diffstat (limited to 'player-android/test')
-rw-r--r--player-android/test/json_helpers_test.dart82
1 files changed, 82 insertions, 0 deletions
diff --git a/player-android/test/json_helpers_test.dart b/player-android/test/json_helpers_test.dart
new file mode 100644
index 0000000..a0596e2
--- /dev/null
+++ b/player-android/test/json_helpers_test.dart
@@ -0,0 +1,82 @@
+// Unit tests for json_helpers.dart.
+//
+// `dateTimeFromJson` is the shared deserialization helper used by every
+// model that has a nullable DateTime field. These tests pin down its
+// defensive contract: only well-formed ISO-8601 strings produce a
+// DateTime; everything else (null, non-string, empty string, malformed
+// string) silently degrades to null without throwing.
+//
+// The malformed-string case is the regression guard for the original
+// crash bug: prior to the fix, `DateTime.parse('not a date')` propagated
+// a FormatException out of fromJson() and crashed the app.
+
+import 'package:flutter_test/flutter_test.dart';
+import 'package:player_android/models/json_helpers.dart';
+
+void main() {
+ group('dateTimeFromJson', () {
+ test('returns null for null input', () {
+ expect(dateTimeFromJson(null), isNull);
+ });
+
+ test('returns null for empty string', () {
+ expect(dateTimeFromJson(''), isNull);
+ });
+
+ test('returns null for non-string input (int)', () {
+ expect(dateTimeFromJson(12345), isNull);
+ });
+
+ test('returns null for non-string input (bool)', () {
+ expect(dateTimeFromJson(true), isNull);
+ });
+
+ test('parses a valid ISO-8601 UTC string', () {
+ final dt = dateTimeFromJson('2026-05-20T12:34:56.000Z');
+ expect(dt, isNotNull);
+ expect(dt!.toUtc().year, 2026);
+ expect(dt.toUtc().month, 5);
+ expect(dt.toUtc().day, 20);
+ expect(dt.toUtc().hour, 12);
+ expect(dt.toUtc().minute, 34);
+ expect(dt.toUtc().second, 56);
+ });
+
+ test('parses a valid ISO-8601 date-only string', () {
+ final dt = dateTimeFromJson('2026-01-15');
+ expect(dt, isNotNull);
+ expect(dt!.year, 2026);
+ expect(dt.month, 1);
+ expect(dt.day, 15);
+ });
+
+ test('returns null for malformed string instead of throwing', () {
+ // Regression guard: this used to throw FormatException and crash
+ // the entire model deserialization.
+ expect(() => dateTimeFromJson('not a date'), returnsNormally);
+ expect(dateTimeFromJson('not a date'), isNull);
+ });
+
+ test('returns null for ISO-like string with non-numeric junk', () {
+ // DateTime.parse is surprisingly tolerant of out-of-range numeric
+ // components (it rolls them over), so we use a clearly non-numeric
+ // value to force a FormatException.
+ expect(dateTimeFromJson('2026-XX-YYTzz:zz:zzZ'), isNull);
+ });
+
+ test('returns null for garbage tokens', () {
+ expect(dateTimeFromJson('@@@'), isNull);
+ });
+ });
+
+ group('dateTimeToJson', () {
+ test('returns null for null input', () {
+ expect(dateTimeToJson(null), isNull);
+ });
+
+ test('serializes DateTime to ISO-8601 string', () {
+ final dt = DateTime.utc(2026, 5, 20, 12, 34, 56);
+ expect(dateTimeToJson(dt), '2026-05-20T12:34:56.000Z');
+ });
+ });
+}