Files
Client/test/api/marianumconnect/ticker_models_test.dart
T

203 lines
6.4 KiB
Dart

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<Map<String, dynamic>>());
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<dynamic>(
requestOptions: requestOptions,
statusCode: 404,
data: {'error': 'CONTENT_UNAVAILABLE', 'webUrl': '/ticker/p/foo'},
),
),
);
expect(
() => GetTickerPage('foo', dio: dio).run(),
throwsA(
isA<TickerContentUnavailableException>()
.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<dynamic>(
requestOptions: requestOptions,
statusCode: 404,
data: {'error': 'NOT_FOUND', 'webUrl': '/ticker/p/foo'},
),
),
);
expect(
() => GetTickerPage('foo', dio: dio).run(),
throwsA(isNot(isA<TickerContentUnavailableException>())),
);
});
});
}
/// 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<Response<T>> get<T>(
String path, {
Object? data,
Map<String, dynamic>? queryParameters,
Options? options,
CancelToken? cancelToken,
ProgressCallback? onReceiveProgress,
}) => Future<Response<T>>.error(error);
@override
dynamic noSuchMethod(Invocation invocation) =>
super.noSuchMethod(invocation);
}