Coverage for apps / ai / fixtures.py: 0%
49 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-02-14 19:13 +0000
« prev ^ index » next coverage.py v7.13.4, created at 2026-02-14 19:13 +0000
1"""Realistic AI response fixtures for testing.
3These fixtures represent actual AI responses including edge cases like:
4- Missing optional fields
5- Null values where allowed
6- Various string formats
7- Edge case data values
8"""
10# Valid responses for each prompt type
11VALID_RECIPE_REMIX = {
12 "title": "Vegan Thai Peanut Noodles",
13 "description": "A plant-based twist on classic pad thai with creamy peanut sauce",
14 "ingredients": [
15 "8 oz rice noodles",
16 "1/4 cup creamy peanut butter",
17 "3 tbsp soy sauce",
18 "2 tbsp rice vinegar",
19 "1 tbsp maple syrup",
20 "1 tbsp sesame oil",
21 "2 cloves garlic, minced",
22 "1 block extra-firm tofu, pressed and cubed",
23 "2 cups shredded cabbage",
24 "1 red bell pepper, sliced",
25 "1/2 cup green onions, chopped",
26 "1/4 cup crushed peanuts",
27 "Fresh cilantro for garnish",
28 ],
29 "instructions": [
30 "Cook rice noodles according to package directions, drain and set aside.",
31 "In a small bowl, whisk together peanut butter, soy sauce, rice vinegar, maple syrup, and 2 tbsp warm water.",
32 "Heat sesame oil in a large wok or skillet over medium-high heat.",
33 "Add tofu cubes and cook until golden brown on all sides, about 5-7 minutes.",
34 "Add garlic and cook for 30 seconds until fragrant.",
35 "Add cabbage and bell pepper, stir-fry for 3-4 minutes.",
36 "Add cooked noodles and peanut sauce, toss to combine.",
37 "Serve topped with green onions, crushed peanuts, and cilantro.",
38 ],
39 "prep_time": "15 minutes",
40 "cook_time": "20 minutes",
41 "total_time": "35 minutes",
42 "yields": "4 servings",
43}
45VALID_RECIPE_REMIX_MINIMAL = {
46 "title": "Simple Remix",
47 "description": "A simple recipe variation",
48 "ingredients": ["ingredient 1", "ingredient 2"],
49 "instructions": ["Step 1", "Step 2"],
50}
52VALID_SERVING_ADJUSTMENT = {
53 "ingredients": [
54 "2 cups all-purpose flour",
55 "1 cup granulated sugar",
56 "3/4 cup unsalted butter, softened",
57 "4 large eggs",
58 "1 cup whole milk",
59 "2 tsp vanilla extract",
60 "2 tsp baking powder",
61 "1/2 tsp salt",
62 ],
63 "instructions": [
64 "Preheat oven to 350°F (175°C). Grease and flour two 9-inch round cake pans.",
65 "In a large bowl, cream together butter and sugar until light and fluffy.",
66 "Beat in eggs one at a time, then stir in vanilla.",
67 "Combine flour, baking powder, and salt; gradually blend into the creamed mixture.",
68 "Stir in milk until batter is smooth.",
69 "Pour batter into prepared pans.",
70 "Bake for 30-35 minutes or until a toothpick inserted comes out clean.",
71 ],
72 "notes": [
73 "Butter should be room temperature for best results",
74 "For even layers, use a kitchen scale to divide batter equally",
75 ],
76 "prep_time": "20 minutes",
77 "cook_time": "35 minutes",
78 "total_time": "55 minutes",
79}
81VALID_SERVING_ADJUSTMENT_MINIMAL = {
82 "ingredients": ["1 cup flour", "1/2 cup sugar"],
83}
85VALID_SERVING_ADJUSTMENT_WITH_NULLS = {
86 "ingredients": ["2 cups rice", "1 can coconut milk", "1 tbsp curry paste"],
87 "notes": ["Best served fresh"],
88 "prep_time": None,
89 "cook_time": "25 minutes",
90 "total_time": None,
91}
93VALID_TIPS_GENERATION = [
94 "Let the dough rest for at least 30 minutes before rolling for a more tender crust.",
95 "Brush the crust with egg wash for a golden, shiny finish.",
96 "Blind bake the crust with pie weights to prevent shrinking and bubbling.",
97 "Allow the pie to cool completely before slicing for clean cuts.",
98]
100VALID_TIPS_GENERATION_MAX = [
101 "Tip 1: Use room temperature ingredients",
102 "Tip 2: Preheat your oven properly",
103 "Tip 3: Measure ingredients accurately",
104 "Tip 4: Follow the recipe order",
105 "Tip 5: Let it rest before serving",
106]
108VALID_TIMER_NAMING = {"label": "Simmer sauce"}
110VALID_REMIX_SUGGESTIONS = [
111 "Make it vegan by substituting eggs with flax eggs and dairy with oat milk",
112 "Add a spicy kick with jalapeños and cayenne pepper",
113 "Make it gluten-free using almond flour and gluten-free baking powder",
114 "Create a Mediterranean version with olive oil, feta, and sun-dried tomatoes",
115 "Make it keto-friendly by replacing sugar with erythritol and flour with almond flour",
116 "Add extra protein with Greek yogurt and protein powder",
117]
119VALID_DISCOVER_SUGGESTIONS = [
120 {
121 "search_query": "autumn pumpkin soup",
122 "title": "Cozy Pumpkin Soup",
123 "description": "A warming soup perfect for fall evenings",
124 },
125 {
126 "search_query": "maple glazed roasted vegetables",
127 "title": "Maple Roasted Root Vegetables",
128 "description": "Sweet and savory side dish featuring seasonal roots",
129 },
130 {
131 "search_query": "apple cinnamon overnight oats",
132 "title": "Apple Pie Overnight Oats",
133 "description": "Grab-and-go breakfast with autumn flavors",
134 },
135]
137VALID_SEARCH_RANKING = [2, 0, 4, 1, 3]
139VALID_SELECTOR_REPAIR = {
140 "suggestions": [".recipe-title", "h1.recipe-name", "[data-recipe-title]"],
141 "confidence": 0.85,
142}
144VALID_SELECTOR_REPAIR_LOW_CONFIDENCE = {
145 "suggestions": [".maybe-title"],
146 "confidence": 0.3,
147}
149VALID_NUTRITION_ESTIMATE = {
150 "calories": "350 kcal",
151 "carbohydrateContent": "45g",
152 "proteinContent": "12g",
153 "fatContent": "14g",
154 "saturatedFatContent": "3g",
155 "unsaturatedFatContent": "9g",
156 "cholesterolContent": "25mg",
157 "sodiumContent": "480mg",
158 "fiberContent": "6g",
159}
161VALID_NUTRITION_ESTIMATE_MINIMAL = {
162 "calories": "200 kcal",
163}
165# Edge case responses (still valid but unusual)
166EDGE_CASE_EMPTY_ARRAYS = {
167 "ingredients": [],
168}
170EDGE_CASE_LONG_STRINGS = {
171 "title": "A" * 500,
172 "description": "B" * 1000,
173 "ingredients": ["Very long ingredient " * 20],
174 "instructions": ["Step " * 100],
175}
177EDGE_CASE_SPECIAL_CHARACTERS = {
178 "title": "Crème Brûlée with Café & Piña Colada",
179 "description": "A dessert with ñ, ü, é, and 中文 characters",
180 "ingredients": ["½ cup milk", "⅓ tsp vanilla", "1°C chilled cream"],
181 "instructions": ["Heat to 180°F", 'Add "fresh" ingredients'],
182}
184EDGE_CASE_UNICODE_FRACTIONS = {
185 "ingredients": ["½ cup flour", "¼ tsp salt", "⅔ cup milk", "⅛ tsp pepper"],
186}
188# Invalid responses for testing validation failures
189INVALID_RECIPE_REMIX_MISSING_TITLE = {
190 "description": "A recipe without a title",
191 "ingredients": ["flour", "sugar"],
192 "instructions": ["Mix", "Bake"],
193}
195INVALID_RECIPE_REMIX_MISSING_INGREDIENTS = {
196 "title": "Incomplete Recipe",
197 "description": "Missing ingredients",
198 "instructions": ["Step 1"],
199}
201INVALID_RECIPE_REMIX_WRONG_TYPE_INGREDIENTS = {
202 "title": "Bad Recipe",
203 "description": "Ingredients should be array",
204 "ingredients": "flour, sugar, eggs",
205 "instructions": ["Mix all"],
206}
208INVALID_RECIPE_REMIX_WRONG_TYPE_TITLE = {
209 "title": 123,
210 "description": "Title should be string",
211 "ingredients": ["flour"],
212 "instructions": ["mix"],
213}
215INVALID_TIPS_TOO_FEW = ["Tip 1", "Tip 2"]
217INVALID_TIPS_TOO_MANY = [
218 "Tip 1",
219 "Tip 2",
220 "Tip 3",
221 "Tip 4",
222 "Tip 5",
223 "Tip 6", # Max is 5
224]
226INVALID_TIPS_WRONG_TYPE = {"tips": ["This should be an array, not object"]}
228INVALID_TIPS_WRONG_ITEM_TYPE = ["Tip 1", "Tip 2", 123, "Tip 4"]
230INVALID_REMIX_SUGGESTIONS_WRONG_COUNT = ["Suggestion 1", "Suggestion 2", "Suggestion 3"]
232INVALID_TIMER_NAMING_MISSING_LABEL = {"name": "Wrong field name"}
234INVALID_SEARCH_RANKING_WRONG_TYPE = [1, 2, "three", 4]
236INVALID_SELECTOR_REPAIR_MISSING_CONFIDENCE = {
237 "suggestions": [".selector"],
238}
240INVALID_SELECTOR_REPAIR_WRONG_CONFIDENCE_TYPE = {
241 "suggestions": [".selector"],
242 "confidence": "high",
243}
245INVALID_DISCOVER_MISSING_QUERY = [
246 {
247 "title": "Missing Query",
248 "description": "No search_query field",
249 }
250]
252INVALID_DISCOVER_WRONG_ITEM_TYPE = ["This should be an object, not a string"]
254INVALID_NUTRITION_MISSING_CALORIES = {
255 "proteinContent": "12g",
256 "fatContent": "8g",
257}
259INVALID_SERVING_ADJUSTMENT_MISSING_INGREDIENTS = {
260 "notes": ["Some notes"],
261 "prep_time": "10 minutes",
262}
264INVALID_SERVING_ADJUSTMENT_WRONG_TIME_TYPE = {
265 "ingredients": ["flour", "sugar"],
266 "prep_time": 15, # Should be string or null
267}
269# Malformed/hallucinated responses
270MALFORMED_JSON_STRING = '{"title": "Incomplete'
272HALLUCINATED_EXTRA_FIELDS = {
273 "title": "Recipe Title",
274 "description": "A recipe",
275 "ingredients": ["flour"],
276 "instructions": ["mix"],
277 "hallucinated_field": "AI added this unexpectedly",
278 "another_fake_field": ["more hallucinated data"],
279}
281HALLUCINATED_NUTRITION_IMPOSSIBLE = {
282 "calories": "5000 kcal", # Unrealistic for a single serving
283 "proteinContent": "500g", # Impossible protein content
284 "fatContent": "-10g", # Negative value
285}
287# Empty/null edge cases
288EMPTY_OBJECT = {}
289EMPTY_ARRAY = []
290NULL_RESPONSE = None
292# Boolean confusion (AI sometimes returns booleans instead of expected types)
293BOOLEAN_CONFUSION = {
294 "title": True,
295 "description": False,
296 "ingredients": True,
297 "instructions": False,
298}
301def get_fixture(name: str):
302 """Get a fixture by name.
304 Args:
305 name: The fixture name (matches variable names above).
307 Returns:
308 The fixture data.
310 Raises:
311 KeyError: If fixture not found.
312 """
313 fixtures = {
314 "valid_recipe_remix": VALID_RECIPE_REMIX,
315 "valid_recipe_remix_minimal": VALID_RECIPE_REMIX_MINIMAL,
316 "valid_serving_adjustment": VALID_SERVING_ADJUSTMENT,
317 "valid_serving_adjustment_minimal": VALID_SERVING_ADJUSTMENT_MINIMAL,
318 "valid_serving_adjustment_with_nulls": VALID_SERVING_ADJUSTMENT_WITH_NULLS,
319 "valid_tips_generation": VALID_TIPS_GENERATION,
320 "valid_tips_generation_max": VALID_TIPS_GENERATION_MAX,
321 "valid_timer_naming": VALID_TIMER_NAMING,
322 "valid_remix_suggestions": VALID_REMIX_SUGGESTIONS,
323 "valid_discover_suggestions": VALID_DISCOVER_SUGGESTIONS,
324 "valid_search_ranking": VALID_SEARCH_RANKING,
325 "valid_selector_repair": VALID_SELECTOR_REPAIR,
326 "valid_selector_repair_low_confidence": VALID_SELECTOR_REPAIR_LOW_CONFIDENCE,
327 "valid_nutrition_estimate": VALID_NUTRITION_ESTIMATE,
328 "valid_nutrition_estimate_minimal": VALID_NUTRITION_ESTIMATE_MINIMAL,
329 "invalid_recipe_remix_missing_title": INVALID_RECIPE_REMIX_MISSING_TITLE,
330 "invalid_recipe_remix_missing_ingredients": INVALID_RECIPE_REMIX_MISSING_INGREDIENTS,
331 "invalid_recipe_remix_wrong_type_ingredients": INVALID_RECIPE_REMIX_WRONG_TYPE_INGREDIENTS,
332 "invalid_recipe_remix_wrong_type_title": INVALID_RECIPE_REMIX_WRONG_TYPE_TITLE,
333 "invalid_tips_too_few": INVALID_TIPS_TOO_FEW,
334 "invalid_tips_too_many": INVALID_TIPS_TOO_MANY,
335 "invalid_tips_wrong_type": INVALID_TIPS_WRONG_TYPE,
336 "invalid_tips_wrong_item_type": INVALID_TIPS_WRONG_ITEM_TYPE,
337 "invalid_remix_suggestions_wrong_count": INVALID_REMIX_SUGGESTIONS_WRONG_COUNT,
338 "invalid_timer_naming_missing_label": INVALID_TIMER_NAMING_MISSING_LABEL,
339 "invalid_search_ranking_wrong_type": INVALID_SEARCH_RANKING_WRONG_TYPE,
340 "invalid_selector_repair_missing_confidence": INVALID_SELECTOR_REPAIR_MISSING_CONFIDENCE,
341 "invalid_selector_repair_wrong_confidence_type": INVALID_SELECTOR_REPAIR_WRONG_CONFIDENCE_TYPE,
342 "invalid_discover_missing_query": INVALID_DISCOVER_MISSING_QUERY,
343 "invalid_discover_wrong_item_type": INVALID_DISCOVER_WRONG_ITEM_TYPE,
344 "invalid_nutrition_missing_calories": INVALID_NUTRITION_MISSING_CALORIES,
345 "invalid_serving_adjustment_missing_ingredients": INVALID_SERVING_ADJUSTMENT_MISSING_INGREDIENTS,
346 "invalid_serving_adjustment_wrong_time_type": INVALID_SERVING_ADJUSTMENT_WRONG_TIME_TYPE,
347 }
348 if name not in fixtures:
349 raise KeyError(f"Fixture not found: {name}")
350 return fixtures[name]