Files
plezy/test/services/live_session_tracker_test.dart
T
edde746 61344f7862 test: extract shared fixtures and scaffolds
Collapse duplicated setup across the suite into six shared helpers under
test/test_helpers/ and rewrite the 28 suites that were open-coding it:

  http_fixtures.dart         jsonResponse() for http.Response JSON stubs
  library_tab_scaffold.dart  pumps library tabs under their required ancestors
  multi_server_fixtures.dart MultiServerProvider wiring for widget tests
  playback_report_fakes.dart PlaybackReportCall + fake report sinks
  profile_stack.dart         production-shaped profile dependency graph
  theme.dart                 testMonoTokens for fast-settling widget tests

Net -1245 lines with no change in coverage or assertions.
2026-07-26 06:09:47 +02:00

95 lines
2.8 KiB
Dart

import 'dart:async';
import 'package:flutter_test/flutter_test.dart';
import 'package:plezy/services/jellyfin_client.dart';
import 'package:plezy/services/live_session_tracker.dart';
import '../test_helpers/playback_report_fakes.dart';
class _FakeJellyfinClient with PlaybackReportRecorder implements JellyfinClient {
final calls = <String>[];
final startGate = Completer<void>();
@override
Future<void> onPlaybackReport(PlaybackReportCall call) async {
final identity = '${call.itemId}:${call.playSessionId}:${call.mediaSourceId}:${call.liveStreamId}';
switch (call.kind) {
case PlaybackReportKind.started:
await startGate.future;
calls.add('started:$identity:${call.playMethod}');
case PlaybackReportKind.progress:
calls.add('${call.isPaused ? 'paused' : 'playing'}:$identity');
case PlaybackReportKind.stopped:
calls.add('stopped:$identity');
}
}
@override
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
}
void main() {
test('coalesces duplicate live starts and orders stop after in-flight start', () async {
final client = _FakeJellyfinClient();
final tracker = JellyfinLiveSessionTracker(
playSessionId: 'live-session-1',
mediaSourceId: 'source-1',
liveStreamId: 'live-stream-1',
playMethod: 'Transcode',
);
final first = tracker.report(
client: client,
itemId: 'channel-1',
state: 'playing',
position: Duration.zero,
duration: Duration.zero,
);
final second = tracker.report(
client: client,
itemId: 'channel-1',
state: 'playing',
position: const Duration(seconds: 1),
duration: Duration.zero,
);
await Future<void>.delayed(Duration.zero);
final stopped = tracker.report(
client: client,
itemId: 'channel-1',
state: 'stopped',
position: const Duration(seconds: 2),
duration: Duration.zero,
);
await Future<void>.delayed(Duration.zero);
expect(client.calls, isEmpty);
client.startGate.complete();
await Future.wait([first, second, stopped]);
expect(client.calls, [
'started:channel-1:live-session-1:source-1:live-stream-1:Transcode',
'stopped:channel-1:live-session-1:source-1:live-stream-1',
]);
});
test('stopping before the first heartbeat still reports the negotiated live identity', () async {
final client = _FakeJellyfinClient();
final tracker = JellyfinLiveSessionTracker(
playSessionId: 'live-session-1',
mediaSourceId: 'source-1',
liveStreamId: 'live-stream-1',
);
await tracker.report(
client: client,
itemId: 'channel-1',
state: 'stopped',
position: Duration.zero,
duration: Duration.zero,
);
expect(client.calls, ['stopped:channel-1:live-session-1:source-1:live-stream-1']);
});
}