120 lines
4.3 KiB
Dart
120 lines
4.3 KiB
Dart
import 'dart:convert';
|
|
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:glp1_companion/features/nutrition/data/datasources/usda_fdc_client.dart';
|
|
import 'package:http/http.dart' as http;
|
|
import 'package:http/testing.dart';
|
|
|
|
/// Representative USDA FoodData Central `foods/search` payload with the six
|
|
/// nutrient IDs the client maps.
|
|
String _fdcSearchResponse() => jsonEncode({
|
|
'totalHits': 1,
|
|
'foods': [
|
|
{
|
|
'fdcId': 12345,
|
|
'description': 'YOGURT, PLAIN, WHOLE MILK',
|
|
'dataType': 'Foundation',
|
|
'foodNutrients': [
|
|
{'nutrientId': 1008, 'value': 61},
|
|
{'nutrientId': 1003, 'value': 3.5},
|
|
{'nutrientId': 1004, 'value': 3.3},
|
|
{'nutrientId': 1005, 'value': 4.7},
|
|
{'nutrientId': 1079, 'value': 0},
|
|
// USDA reports sodium (1093) in mg directly — unlike OFF, which
|
|
// reports grams; the client passes it through unchanged.
|
|
{'nutrientId': 1093, 'value': 46},
|
|
],
|
|
},
|
|
],
|
|
});
|
|
|
|
void main() {
|
|
group('UsdaFdcClient request shape', () {
|
|
test('sends api_key as a QUERY parameter, never in the JSON body',
|
|
() async {
|
|
late http.Request captured;
|
|
final client = MockClient((request) async {
|
|
captured = request;
|
|
return http.Response(_fdcSearchResponse(), 200);
|
|
});
|
|
|
|
final usda = UsdaFdcClient(client: client, apiKey: 'TEST_KEY');
|
|
await usda.searchByText('yogurt');
|
|
|
|
// Regression (field-reported 2026-10-17): the FDC API only validates
|
|
// the key from the query string — a body-only `apiKey` yields
|
|
// HTTP 403 API_KEY_MISSING.
|
|
expect(captured.url.queryParameters['api_key'], 'TEST_KEY');
|
|
expect(captured.url.queryParameters.containsKey('apiKey'), isFalse);
|
|
|
|
final body = jsonDecode(captured.body) as Map<String, dynamic>;
|
|
expect(body.containsKey('apiKey'), isFalse,
|
|
reason: 'the JSON body must not carry the key');
|
|
expect(body['query'], 'yogurt');
|
|
});
|
|
|
|
test('falls back to DEMO_KEY when no override or .env entry exists',
|
|
() async {
|
|
late Uri capturedUrl;
|
|
final client = MockClient((request) async {
|
|
capturedUrl = request.url;
|
|
return http.Response(_fdcSearchResponse(), 200);
|
|
});
|
|
|
|
// No constructor key; dotenv is uninitialized in tests, so the client
|
|
// must degrade to the public DEMO_KEY.
|
|
await UsdaFdcClient(client: client).searchByText('yogurt');
|
|
|
|
expect(capturedUrl.queryParameters['api_key'], 'DEMO_KEY');
|
|
});
|
|
|
|
test('maps nutrient IDs onto the FoodProduct entity', () async {
|
|
final client = MockClient((request) async {
|
|
return http.Response(_fdcSearchResponse(), 200);
|
|
});
|
|
|
|
final results =
|
|
await UsdaFdcClient(client: client, apiKey: 'k').searchByText('yogurt');
|
|
|
|
expect(results, hasLength(1));
|
|
final product = results.single;
|
|
expect(product.id, 'fdc:12345');
|
|
expect(product.productName, 'YOGURT, PLAIN, WHOLE MILK');
|
|
expect(product.calories, 61);
|
|
expect(product.proteinGrams, 3.5);
|
|
expect(product.fatGrams, 3.3);
|
|
expect(product.carbsGrams, 4.7);
|
|
expect(product.fiberGrams, 0);
|
|
expect(product.sodiumMg, 46, reason: 'grams are converted to mg');
|
|
expect(product.servingSizeGrams, 100);
|
|
expect(product.servingSizeUnit, 'g',
|
|
reason: 'FDC defaults to a per-100 g basis when no unit is given');
|
|
});
|
|
|
|
test('barcode lookups restrict the query to Branded foods', () async {
|
|
late Map<String, dynamic> capturedBody;
|
|
final client = MockClient((request) async {
|
|
capturedBody = jsonDecode(request.body) as Map<String, dynamic>;
|
|
return http.Response(_fdcSearchResponse(), 200);
|
|
});
|
|
|
|
await UsdaFdcClient(client: client, apiKey: 'k')
|
|
.lookupBarcode('012345678905');
|
|
|
|
expect(capturedBody['query'], '012345678905');
|
|
expect(capturedBody['dataType'], ['Branded']);
|
|
});
|
|
|
|
test('non-200 responses throw UsdaFdcException', () async {
|
|
final client = MockClient(
|
|
(request) async => http.Response('{"error":{"code":"API_KEY_MISSING"}}', 403),
|
|
);
|
|
|
|
final usda = UsdaFdcClient(client: client, apiKey: 'bad');
|
|
expect(
|
|
usda.searchByText('yogurt'),
|
|
throwsA(isA<UsdaFdcException>()),
|
|
);
|
|
});
|
|
});
|
|
} |