import 'package:dio/dio.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:marianum_mobile/api/errors/ticker_content_unavailable_exception.dart'; import 'package:marianum_mobile/api/marianumconnect/queries/get_ticker/get_ticker_response.dart'; import 'package:marianum_mobile/api/marianumconnect/queries/get_ticker_nav/get_ticker_nav_response.dart'; import 'package:marianum_mobile/api/marianumconnect/queries/get_ticker_page/get_ticker_page.dart'; import 'package:marianum_mobile/api/marianumconnect/queries/get_ticker_page/get_ticker_page_response.dart'; void main() { group('TickerResponse.fromJson', () { test('available post carries the nested content object', () { final response = TickerResponse.fromJson({ 'schemaVersion': 1, 'available': true, 'hash': 'R123:uuid@2026-07-08T10:00:00', 'publishedAt': '2026-07-08T10:00:00', 'webUrl': '/ticker', 'content': { 'type': 'doc', 'content': [ { 'type': 'paragraph', 'content': [ {'type': 'text', 'text': 'Hallo'}, ], }, ], }, }); expect(response.available, isTrue); expect(response.schemaVersion, 1); expect(response.webUrl, '/ticker'); expect(response.content, isA>()); expect(response.content!['type'], 'doc'); }); test('unavailable post keeps content null and defaults safely', () { final response = TickerResponse.fromJson({ 'schemaVersion': 1, 'available': false, 'webUrl': '/ticker', 'content': null, }); expect(response.available, isFalse); expect(response.content, isNull); expect(response.hash, isNull); }); }); group('TickerNavResponse.fromJson', () { test('parses sections with per-kind fields', () { final nav = TickerNavResponse.fromJson({ 'schemaVersion': 1, 'navHash': 'nav-abc', 'sections': [ { 'title': 'Informationen', 'pages': [ {'title': 'Über uns', 'slug': 'ueber-uns', 'kind': 'CONTENT'}, { 'title': 'Webseite', 'slug': 'web', 'kind': 'REDIRECT', 'externalUrl': 'https://marianum-fulda.de', 'externalUrlNewTab': true, }, { 'title': 'Flyer', 'slug': 'flyer', 'kind': 'PROXIED_FILE', 'fileUrl': 'ticker/pages/flyer/file', }, ], }, ], }); expect(nav.navHash, 'nav-abc'); expect(nav.sections, hasLength(1)); final pages = nav.sections.single.pages; expect(pages.map((p) => p.kind), ['CONTENT', 'REDIRECT', 'PROXIED_FILE']); expect(pages[1].externalUrl, 'https://marianum-fulda.de'); expect(pages[1].externalUrlNewTab, isTrue); expect(pages[2].fileUrl, 'ticker/pages/flyer/file'); }); test('missing sections default to empty', () { final nav = TickerNavResponse.fromJson({ 'schemaVersion': 1, 'navHash': '', }); expect(nav.sections, isEmpty); }); }); group('TickerPageResponse.fromJson', () { test('CONTENT page exposes the nested document', () { final page = TickerPageResponse.fromJson({ 'schemaVersion': 1, 'slug': 'ueber-uns', 'title': 'Über uns', 'kind': 'CONTENT', 'content': {'type': 'doc', 'content': []}, }); expect(page.kind, TickerPageKind.content); expect(page.content, isNotNull); }); test('REDIRECT page exposes the external URL', () { final page = TickerPageResponse.fromJson({ 'schemaVersion': 1, 'kind': 'REDIRECT', 'externalUrl': 'https://marianum-fulda.de', }); expect(page.kind, TickerPageKind.redirect); expect(page.externalUrl, 'https://marianum-fulda.de'); }); test('PROXIED_FILE page exposes file metadata', () { final page = TickerPageResponse.fromJson({ 'schemaVersion': 1, 'kind': 'PROXIED_FILE', 'fileUrl': 'ticker/pages/flyer/file', 'contentType': 'application/pdf', 'filename': 'flyer.pdf', }); expect(page.kind, TickerPageKind.proxiedFile); expect(page.contentType, 'application/pdf'); expect(page.filename, 'flyer.pdf'); }); }); group('GetTickerPage error mapping', () { test('404 CONTENT_UNAVAILABLE maps to a typed exception with webUrl', () async { final requestOptions = RequestOptions(path: 'ticker/pages/foo'); final dio = _ThrowingDio( DioException( requestOptions: requestOptions, type: DioExceptionType.badResponse, response: Response( requestOptions: requestOptions, statusCode: 404, data: {'error': 'CONTENT_UNAVAILABLE', 'webUrl': '/ticker/p/foo'}, ), ), ); expect( () => GetTickerPage('foo', dio: dio).run(), throwsA( isA() .having((e) => e.webUrl, 'webUrl', '/ticker/p/foo') .having((e) => e.allowRetry, 'allowRetry', isFalse), ), ); }); test('other 404 (NOT_FOUND) does not become CONTENT_UNAVAILABLE', () async { final requestOptions = RequestOptions(path: 'ticker/pages/foo'); final dio = _ThrowingDio( DioException( requestOptions: requestOptions, type: DioExceptionType.badResponse, response: Response( requestOptions: requestOptions, statusCode: 404, data: {'error': 'NOT_FOUND', 'webUrl': '/ticker/p/foo'}, ), ), ); expect( () => GetTickerPage('foo', dio: dio).run(), throwsA(isNot(isA())), ); }); }); } /// Minimal fake Dio whose `get` always fails with the given exception; every /// other member is unused. class _ThrowingDio implements Dio { final DioException error; _ThrowingDio(this.error); @override Future> get( String path, { Object? data, Map? queryParameters, Options? options, CancelToken? cancelToken, ProgressCallback? onReceiveProgress, }) => Future>.error(error); @override dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); }