import 'package:dio/dio.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:frosty/apis/base_api_client.dart'; import 'package:frosty/apis/twitch_gql_api.dart'; import 'package:http_mock_adapter/http_mock_adapter.dart'; void main() { late Dio dio; late DioAdapter dioAdapter; late TwitchGqlApi api; setUp(() { dio = Dio(BaseOptions()); dioAdapter = DioAdapter(dio: dio); api = TwitchGqlApi(dio); }); Map streamEdge({ required String id, required String login, required int viewers, String? cursor, }) { return { 'cursor': cursor ?? 'cursor-$id', 'node': { 'id': id, 'title': '$login live', 'viewersCount': viewers, 'createdAt': '2026-01-01T00:00:00Z', 'language': 'en', 'game': {'id': '1', 'name': 'Game'}, 'broadcaster': { 'id': id, 'login': login, 'displayName': login, }, }, }; } group('getStreams', () { test('parses freeform tag page and cursor', () async { dioAdapter.onPost( 'https://gql.twitch.tv/gql', (server) => server.reply(200, { 'data': { 'streams': { 'edges': [ streamEdge(id: '1', login: 'a', viewers: 10), streamEdge(id: '2', login: 'b', viewers: 5), ], 'pageInfo': {'hasNextPage': true}, }, }, }), data: Matchers.any, ); final page = await api.getStreams( freeformTags: ['Deutsch'], sort: StreamViewerSort.viewersAsc, ); expect(page.data.map((s) => s.userLogin), ['a', 'b']); expect(page.data.map((s) => s.viewerCount), [10, 5]); expect(page.pagination['cursor'], 'cursor-2'); }); test('throws IntegrityCheckException on integrity errors', () async { dioAdapter.onPost( 'https://gql.twitch.tv/gql', (server) => server.reply(200, { 'errors': [ {'message': 'failed integrity check'}, ], 'data': {'streams': null}, }), data: Matchers.any, ); await expectLater( api.getStreams( freeformTags: ['Deutsch'], cursor: 'page-2', authToken: 'web', integrityToken: 'stale', deviceId: 'device', ), throwsA(isA()), ); }); test('sends Client-Integrity and X-Device-Id when provided', () async { dioAdapter.onPost( 'https://gql.twitch.tv/gql', (server) => server.reply(200, { 'data': { 'streams': { 'edges': [streamEdge(id: '1', login: 'a', viewers: 1)], 'pageInfo': {'hasNextPage': false}, }, }, }), data: Matchers.any, headers: { 'Client-ID': Matchers.any, 'Content-Type': Matchers.any, 'User-Agent': Matchers.any, 'Authorization': 'OAuth web-token', 'X-Device-Id': 'dev-123', 'Client-Integrity': 'integrity-token', }, ); final page = await api.getStreams( freeformTags: ['English'], sort: StreamViewerSort.viewersDesc, cursor: 'after-1', authToken: 'web-token', integrityToken: 'integrity-token', deviceId: 'dev-123', ); expect(page.data, hasLength(1)); expect(page.pagination, isEmpty); }); }); }