import 'package:flutter_test/flutter_test.dart';
import 'package:http/http.dart' as http;
import 'package:http/testing.dart';
import 'package:consumer_app/core/networking/api_client.dart';
import 'package:consumer_app/core/errors/api_error_code.dart';
void main() {
test('sends the selected locale and query contract', () async {
Uri? requestedUri;
String? requestedLocale;
final client = MockClient((request) async {
requestedUri = request.url;
requestedLocale = request.headers['Accept-Language'];
return http.Response('{"items":[],"meta":{"total":0}}', 200);
});
final api = ApiClient(
baseUrl: Uri.parse('https://example.test/wp-json/meinweimar/v1'),
locale: () => 'en-US',
client: client,
);
final response = await api.getJson(
'/discover',
queryParameters: {'types': 'places,events', 'radius_km': '10'},
);
expect(response['meta'], isA<Map<String, dynamic>>());
expect(requestedLocale, 'en-US');
expect(requestedUri?.path, '/wp-json/meinweimar/v1/discover');
expect(requestedUri?.queryParameters['types'], 'places,events');
expect(requestedUri?.queryParameters['radius_km'], '10');
});
test('rewrites local media URLs to the configured device API host', () async {
final api = ApiClient(
baseUrl: Uri.parse('http://10.0.2.2:8888/wp-json/meinweimar/v1'),
locale: () => 'de-DE',
client: MockClient(
(_) async => http.Response(
'{"hero":{"image_url":"http://localhost:8888/uploads/hero.jpg"},'
'"items":[{"image_url":"http://127.0.0.1:8888/uploads/item.jpg",'
'"external":"https://cdn.example.test/item.jpg"}]}',
200,
),
),
);
final response = await api.getJson('/bootstrap');
expect(
(response['hero'] as Map<String, dynamic>)['image_url'],
'http://10.0.2.2:8888/uploads/hero.jpg',
);
expect(
((response['items'] as List).single as Map<String, dynamic>)['image_url'],
'http://10.0.2.2:8888/uploads/item.jpg',
);
expect(
((response['items'] as List).single as Map<String, dynamic>)['external'],
'https://cdn.example.test/item.jpg',
);
});
test('sends JSON profile updates through PUT', () async {
final api = ApiClient(
baseUrl: Uri.parse('https://example.test/wp-json/meinweimar/v1'),
locale: () => 'de-DE',
client: MockClient((request) async {
expect(request.method, 'PUT');
expect(request.url.path, '/wp-json/meinweimar/v1/me');
expect(request.headers['content-type'], 'application/json');
expect(request.body, '{"nickname":"Lokal"}');
return http.Response(
'{"user_id":8,"visible":true,"nickname":"Lokal"}',
200,
);
}),
);
final response = await api.putJson(
'/me',
body: const {'nickname': 'Lokal'},
);
expect(response['nickname'], 'Lokal');
});
test('builds the authenticated image suggestion multipart contract', () async {
final api = ApiClient(
baseUrl: Uri.parse('https://example.test/wp-json/meinweimar/v1'),
locale: () => 'de-DE',
client: MockClient((request) async {
final multipart = request;
expect(multipart.url.path, '/wp-json/meinweimar/v1/image-suggestions');
expect(
multipart.headers['content-type'],
startsWith('multipart/form-data;'),
);
expect(
multipart.headers['idempotency-key'],
'image-suggestion-test-1234',
);
expect(multipart.body, contains('name="target_type"'));
expect(multipart.body, contains('places'));
expect(multipart.body, contains('name="target_id"'));
expect(multipart.body, contains('name="rights_holder_confirmed"'));
expect(multipart.body, contains('filename="place.jpg"'));
return http.Response(
'{"suggestion":{"id":4,"target_type":"places","target_id":7,"status":"submitted"}}',
201,
);
}),
);
final response = await api.postMultipart(
'/image-suggestions',
fields: {
'target_type': 'places',
'target_id': '7',
'rights_holder_confirmed': 'true',
},
bytes: [1, 2, 3],
filename: 'place.jpg',
idempotencyKey: 'image-suggestion-test-1234',
);
expect(response['suggestion'], isA<Map<String, dynamic>>());
});
test('keeps stable API error codes for image suggestion conflicts', () async {
final api = ApiClient(
baseUrl: Uri.parse('https://example.test/wp-json/meinweimar/v1'),
locale: () => 'de-DE',
client: MockClient(
(_) async => http.Response(
'{"code":"meinweimar_image_suggestion_idempotency_conflict"}',
409,
),
),
);
expect(
() => api.getJson('/image-suggestions/4'),
throwsA(
isA<ApiException>()
.having((error) => error.code, 'code', ApiErrorCode.conflict)
.having(
(error) => error.serverCode,
'serverCode',
'meinweimar_image_suggestion_idempotency_conflict',
),
),
);
});
test('sends idempotency for JSON follow-up responses', () async {
final api = ApiClient(
baseUrl: Uri.parse('https://example.test/wp-json/meinweimar/v1'),
locale: () => 'de-DE',
client: MockClient((request) async {
expect(request.method, 'POST');
expect(request.url.path, '/wp-json/meinweimar/v1/image-suggestions/4');
expect(request.headers['idempotency-key'], 'response-test-1234');
expect(request.body, '{"response":"Neue Angaben"}');
return http.Response('{"suggestion":{"id":4}}', 200);
}),
);
final response = await api.postJson(
'/image-suggestions/4',
body: const {'response': 'Neue Angaben'},
idempotencyKey: 'response-test-1234',
);
expect(response['suggestion'], isA<Map<String, dynamic>>());
});
test('redacts response bodies from API exception diagnostics', () async {
final api = ApiClient(
baseUrl: Uri.parse('https://example.test/wp-json/meinweimar/v1'),
locale: () => 'de-DE',
client: MockClient(
(_) async => http.Response(
'{"code":"meinweimar_image_suggestion_duplicate","message":"private detail"}',
409,
),
),
);
try {
await api.getJson('/image-suggestions');
fail('expected an API exception');
} on ApiException catch (error) {
expect(error.serverCode, 'meinweimar_image_suggestion_duplicate');
expect(
error.debugMessage,
'HTTP 409 [meinweimar_image_suggestion_duplicate]',
);
expect(error.debugMessage, isNot(contains('private detail')));
}
});
}
