import 'package:flutter_test/flutter_test.dart';
import 'package:consumer_app/models/account_models.dart';
import 'package:consumer_app/models/claim_models.dart';
import 'package:consumer_app/models/content_models.dart';
import 'package:consumer_app/models/image_suggestion_models.dart';
void main() {
test('maps the shared discovery contract without losing geo privacy', () {
final item = DiscoveryItem.fromJson({
'id': 7,
'resource': 'places',
'terms': {
'mw_place_category': ['culture', 'electronics'],
'mw_tag': ['featured'],
},
'title': 'Café am Markt',
'excerpt': 'Lokaler Treffpunkt',
'media_placeholder': true,
'geo': {
'available': true,
'lat': 50.98,
'lng': 11.33,
'privacy': 'public',
'precision': 'exact',
},
'address': {
'line1': 'Markt 1',
'line2': 'Innenstadt',
'locality': 'Weimar',
'postal_code': '99423',
},
'distance_km': 1.2,
'contact': {'email': 'cafe@example.test'},
'media': [
{
'id': 42,
'url': 'https://example.test/cafe.jpg',
'alt': 'Café am Markt',
'caption': '',
'width': 1200,
'height': 800,
'srcset': 'https://example.test/cafe.jpg 1200w',
'is_placeholder': true,
},
],
'place_details': {
'opening_hours': {
'raw': 'Mo-Fr 09:00-18:00',
'parsed': true,
'entries': [
{
'days': ['mon', 'tue', 'wed', 'thu', 'fri'],
'slots': [
{'open': '09:00', 'close': '18:00'},
],
'closed': false,
},
],
'days': ['mon', 'tue', 'wed', 'thu', 'fri'],
'slots': [
{'open': '09:00', 'close': '18:00'},
],
'closed': false,
'unparsed': [],
},
'rating_average': 4.5,
'rating_count': 12,
'special_offers': 'Mittagsangebot',
'neighbourhood': 'Altstadt',
'featured': true,
},
});
expect(item.id, 7);
expect(item.title, 'Café am Markt');
expect(item.terms['mw_place_category'], ['culture', 'electronics']);
expect(item.terms['mw_tag'], ['featured']);
expect(item.geo.available, isTrue);
expect(item.geo.privacy, 'public');
expect(item.distanceKm, 1.2);
expect(item.address.formatted, 'Markt 1, Innenstadt, 99423 Weimar');
expect(item.contact.email, 'cafe@example.test');
expect(item.media, hasLength(1));
expect(item.media.single.alt, 'Café am Markt');
expect(item.media.single.isPlaceholder, isTrue);
expect(item.mediaPlaceholder, isTrue);
expect(item.placeDetails?.openingHours.raw, 'Mo-Fr 09:00-18:00');
expect(item.placeDetails?.openingHours.entries.single.days, [
'mon',
'tue',
'wed',
'thu',
'fri',
]);
expect(item.placeDetails?.openingHours.days, [
'mon',
'tue',
'wed',
'thu',
'fri',
]);
expect(item.placeDetails?.openingHours.slots.single.open, '09:00');
expect(item.placeDetails?.openingHours.closed, isFalse);
expect(item.placeDetails?.ratingAverage, 4.5);
expect(item.placeDetails?.ratingCount, 12);
expect(item.placeDetails?.featured, isTrue);
});
test('falls back safely for incomplete API items', () {
final item = DiscoveryItem.fromJson(const {});
expect(item.id, 0);
expect(item.resource, 'places');
expect(item.geo.available, isFalse);
expect(item.address.formatted, isEmpty);
expect(item.mediaPlaceholder, isFalse);
});
test('keeps editorial html primary and preserves text fallback', () {
final formatted = DiscoveryItem.fromJson({
'content': {
'html': '<h2>Überschrift</h2><p>Absatz</p><ul><li>Punkt</li></ul>',
'text': 'ÜberschriftAbsatzPunkt',
},
});
final fallback = DiscoveryItem.fromJson({
'content': {'html': '', 'text': 'Nur Klartext'},
});
expect(formatted.content?.html, contains('<h2>'));
expect(formatted.content?.html, contains('<ul>'));
expect(formatted.content?.text, 'ÜberschriftAbsatzPunkt');
expect(fallback.content?.html, isEmpty);
expect(fallback.content?.text, 'Nur Klartext');
});
test('maps typed listing fields without losing the lifecycle date', () {
final item = DiscoveryItem.fromJson({
'resource': 'real-estate',
'listing': {
'status': 'active',
'contact_preference': 'email',
'price_label': '345000 EUR',
'provider_type': 'broker',
'price_type': 'fixed',
'price_amount': 345000,
'price_currency': 'EUR',
'price_interval': 'once',
'available_from': '2026-10-01',
'company_name': 'Weimar Immobilien',
'property_kind': 'apartment',
'property_rooms': 3.5,
'bedrooms': 2,
'energy_class': 'B',
'amenity_balcony': 'yes',
'transaction_type': 'sale',
'area': 92.5,
'bathrooms': 1,
'expire_date': '2026-12-31',
},
});
expect(item.listing?.status, 'active');
expect(item.listing?.companyName, 'Weimar Immobilien');
expect(item.listing?.propertyRooms, 3.5);
expect(item.listing?.offerType, isEmpty);
expect(item.listing?.providerType, 'broker');
expect(item.listing?.priceAmount, 345000);
expect(item.listing?.priceCurrency, 'EUR');
expect(item.listing?.availableFrom, '2026-10-01');
expect(item.listing?.propertyTransactionType, 'sale');
expect(item.listing?.propertyArea, 92.5);
expect(item.listing?.propertyBathrooms, 1);
expect(item.listing?.value('bedrooms'), 2);
expect(item.listing?.value('energy_class'), 'B');
expect(item.listing?.value('amenity_balcony'), 'yes');
expect(item.listing?.expireDate, '2026-12-31');
});
test(
'preserves event ticketing and organizer fields from the REST group',
() {
final item = DiscoveryItem.fromJson({
'resource': 'events',
'event': {
'start': '2026-10-01T18:00:00+02:00',
'end': '2026-10-01T21:00:00+02:00',
'status': 'scheduled',
'venue_place_id': 453,
'ticket_mode': 'external',
'ticket_url': 'https://tickets.example.test/event/1',
'ticket_price_from': 12.5,
'ticket_price_to': 18,
'organizer_name': 'Kulturverein Weimar',
'organizer_url': 'https://example.test/organizer',
'occurrences': [
{
'starts_at': '2026-10-08T18:00:00+02:00',
'ends_at': '2026-10-08T21:00:00+02:00',
'timezone': 'Europe/Berlin',
'status': 'scheduled',
'recurrence': 'weekly',
},
],
},
});
expect(item.event?.start, '2026-10-01T18:00:00+02:00');
expect(item.event?.venuePlaceId, 453);
expect(item.event?.value('ticket_mode'), 'external');
expect(item.event?.value('ticket_price_to'), 18);
expect(item.event?.value('organizer_name'), 'Kulturverein Weimar');
expect(item.event?.occurrences.single.timezone, 'Europe/Berlin');
expect(item.event?.occurrences.single.recurrence, 'weekly');
},
);
test('maps discovery pagination, geo and localization metadata', () {
final response = DiscoveryResponse.fromJson({
'items': const [],
'meta': {
'page': 2,
'per_page': 12,
'total': 25,
'total_pages': 3,
'sort': 'distance',
'status': 'published',
'geo': {'lat': 50.98, 'lng': 11.33, 'radius_km': 25},
'bounds': {
'min_lat': 50.8,
'max_lat': 51.1,
'min_lng': 11.1,
'max_lng': 11.5,
},
'localization': {
'requested_locale': 'en_US',
'fallback_locale': 'de_DE',
'selection': 'requested-or-fallback-or-unlocalized',
},
'categories': [
{
'slug': 'kunst-kultur',
'name': 'Kunst & Kultur',
'parent_slug': '',
'count': 32,
'taxonomy': 'mw_place_category',
},
{
'slug': 'museum-2',
'name': 'Museum',
'parent_slug': 'kunst-kultur',
'count': 12,
'taxonomy': 'mw_place_category',
},
],
},
});
expect(response.total, 25);
expect(response.meta.page, 2);
expect(response.meta.totalPages, 3);
expect(response.meta.sort, 'distance');
expect(response.meta.geo?.radiusKm, 25);
expect(response.meta.bounds?.minLatitude, 50.8);
expect(response.meta.localization.requestedLocale, 'en_US');
expect(response.meta.categories.first.slug, 'kunst-kultur');
expect(response.meta.categories.first.name, 'Kunst & Kultur');
expect(response.meta.categories.last.parentSlug, 'kunst-kultur');
});
test('maps saved items through the shared discovery item contract', () {
final response = SavedResponse.fromJson({
'items': [
{
'object_type': 'places',
'object_id': 9,
'created_at': '2026-09-20 10:00:00',
'item': {'id': 9, 'resource': 'places', 'title': 'Saved place'},
},
],
});
expect(response.items, hasLength(1));
expect(response.items.single.objectId, 9);
expect(response.items.single.item?.title, 'Saved place');
});
test('maps private profile fields without inventing visibility', () {
final profile = ProfileData.fromJson({
'user_id': 12,
'visible': true,
'nickname': 'Weimarer',
'display_name': 'Private Display Name',
'status': 'Unterwegs',
'profile_visibility': 'private',
'interests': ['Kultur', 'Kultur'],
});
expect(profile.userId, 12);
expect(profile.nickname, 'Weimarer');
expect(profile.profileVisibility, 'private');
expect(profile.interests, ['Kultur', 'Kultur']);
});
test('maps claim status records without changing server state', () {
final response = ClaimListResponse.fromJson({
'claims': [
{
'id': 3,
'object_type': 'places',
'object_id': 11,
'claimant_user_id': 8,
'organization_name': 'Weimar Café',
'status': 'pending',
'decision_note': '',
},
],
});
expect(response.claims.single.id, 3);
expect(response.claims.single.objectId, 11);
expect(response.claims.single.status, 'pending');
});
test('maps image suggestion status without exposing quarantine paths', () {
final suggestion = ImageSuggestionData.fromJson({
'id': 4,
'target_type': 'places',
'target_id': 7,
'status': 'submitted',
'original_filename': 'place.jpg',
'file_hash': 'abc',
'checks': {'technical_status': 'passed', 'malware_status': 'pending'},
'request_message': 'Bitte nenne die Bildquelle.',
'submitter_response': '',
'created_at': '2026-09-20 10:00:00',
'updated_at': '2026-09-20 10:00:00',
});
expect(suggestion.id, 4);
expect(suggestion.status, 'submitted');
expect(suggestion.malwareStatus, 'pending');
expect(suggestion.requestMessage, 'Bitte nenne die Bildquelle.');
});
}
