1+ <?php
2+
3+ namespace Tests \Feature \Inventario ;
4+
5+ use Tests \TestCase ;
6+ use Illuminate \Support \Facades \Http ;
7+ use PHPUnit \Framework \Attributes \Test ;
8+
9+ class IngredientesControllerTest extends TestCase
10+ {
11+ // Mock completo de todos los endpoints que usa index()
12+ private function mockIndexApis (): void
13+ {
14+ Http::fake ([
15+ '*/ingredientes/lista ' => Http::response ([
16+ [
17+ 'idIngrediente ' => 1 ,
18+ 'nombreIngrediente ' => 'Harina ' ,
19+ 'referenciaIngrediente ' => 'HAR-001 ' ,
20+ 'idProveedor ' => 1 ,
21+ 'idCategoria ' => 1 ,
22+ 'abreviaturaUnidad ' => 'kg '
23+ ]
24+ ], 200 ),
25+ '*/proveedores ' => Http::response ([
26+ ['idProveedor ' => 1 , 'nombreProv ' => 'Harina Dorada ' ]
27+ ], 200 ),
28+ '*/categorias/ingredientes ' => Http::response ([
29+ ['idCategoriaIngrediente ' => 1 , 'nombreCategoria ' => 'Harinas ' ]
30+ ], 200 ),
31+ '*/unidades-medida ' => Http::response ([
32+ ['idUnidad ' => 1 , 'nombreUnidad ' => 'Kilogramo ' , 'abreviaturaUnidad ' => 'kg ' ]
33+ ], 200 ),
34+ ]);
35+ }
36+
37+ // =========================================================
38+ // index()
39+ // =========================================================
40+
41+ #[Test]
42+ public function index_retorna_200_con_vista_correcta (): void
43+ {
44+ $ this ->mockIndexApis ();
45+
46+ $ response = $ this ->get ('/inventario/ingredientes ' );
47+
48+ $ response ->assertStatus (200 );
49+ $ response ->assertViewIs ('inventarioviews.ingredientes.index ' );
50+ }
51+
52+ #[Test]
53+ public function index_pasa_las_4_variables_a_la_vista (): void
54+ {
55+ $ this ->mockIndexApis ();
56+
57+ $ response = $ this ->get ('/inventario/ingredientes ' );
58+
59+ $ response ->assertViewHas ('ingredientes ' );
60+ $ response ->assertViewHas ('proveedores ' );
61+ $ response ->assertViewHas ('categorias ' );
62+ $ response ->assertViewHas ('unidades ' );
63+ }
64+
65+ #[Test]
66+ public function index_carga_aunque_spring_falle_en_proveedores (): void
67+ {
68+ // Si Spring falla en proveedores/categorias/unidades,
69+ // el try/catch del controller devuelve arrays vacíos
70+ // y la vista igual debe cargar sin explotar
71+ Http::fake ([
72+ '*/ingredientes/lista ' => Http::response ([], 200 ),
73+ '*/proveedores ' => Http::response ([], 500 ),
74+ '*/categorias/* ' => Http::response ([], 500 ),
75+ '*/unidades-medida ' => Http::response ([], 500 ),
76+ ]);
77+
78+ $ response = $ this ->get ('/inventario/ingredientes ' );
79+
80+ // No debe dar 500 — el catch garantiza arrays vacíos
81+ $ response ->assertStatus (200 );
82+ }
83+
84+ // =========================================================
85+ // inventario() — stock
86+ // =========================================================
87+
88+ #[Test]
89+ public function inventario_retorna_vista_con_ingredientes (): void
90+ {
91+ Http::fake ([
92+ '*/ingredientes/cantidad ' => Http::response ([
93+ ['idIngrediente ' => 1 , 'nombreIngrediente ' => 'Harina ' , 'cantidadIngrediente ' => 50.0 ]
94+ ], 200 )
95+ ]);
96+
97+ $ response = $ this ->get ('/inventario/ingredientes/stock ' );
98+
99+ $ response ->assertStatus (200 );
100+ $ response ->assertViewIs ('inventarioviews.ingredientes.inventario ' );
101+ $ response ->assertViewHas ('ingredientes ' );
102+ }
103+
104+ #[Test]
105+ public function inventario_muestra_error_cuando_api_falla (): void
106+ {
107+ Http::fake ([
108+ '*/ingredientes/cantidad ' => Http::response ([], 500 )
109+ ]);
110+
111+ $ response = $ this ->get ('/inventario/ingredientes/stock ' );
112+
113+ $ response ->assertStatus (200 );
114+ $ response ->assertViewIs ('inventarioviews.ingredientes.inventario ' );
115+ $ response ->assertViewHas ('ingredientes ' , []);
116+ $ response ->assertViewHas ('error ' );
117+ }
118+
119+ // =========================================================
120+ // store()
121+ // =========================================================
122+
123+ #[Test]
124+ public function store_con_datos_validos_redirige_con_success (): void
125+ {
126+ Http::fake ([
127+ '*/crearingrediente ' => Http::response ('Creado con éxito ' , 200 )
128+ ]);
129+
130+ $ response = $ this ->post ('/inventario/ingredientes/store ' , [
131+ 'idProveedor ' => 1 ,
132+ 'idCategoria ' => 1 ,
133+ 'idUnidadMedida ' => 1 ,
134+ 'nombreIngrediente ' => 'Harina Test ' ,
135+ 'referenciaIngrediente ' => 'HAR-TEST ' ,
136+ ]);
137+
138+ $ response ->assertRedirect (route ('ingredientes.index ' ));
139+ $ response ->assertSessionHas ('success ' , 'Ingrediente creado con éxito. ' );
140+ }
141+
142+ #[Test]
143+ public function store_falla_si_falta_idProveedor (): void
144+ {
145+ $ response = $ this ->post ('/inventario/ingredientes/store ' , [
146+ 'idCategoria ' => 1 ,
147+ 'idUnidadMedida ' => 1 ,
148+ 'nombreIngrediente ' => 'Harina ' ,
149+ 'referenciaIngrediente ' => 'HAR-001 ' ,
150+ // idProveedor ausente
151+ ]);
152+
153+ $ response ->assertSessionHasErrors (['idProveedor ' ]);
154+ }
155+
156+ #[Test]
157+ public function store_falla_si_todos_los_campos_estan_vacios (): void
158+ {
159+ $ response = $ this ->post ('/inventario/ingredientes/store ' , []);
160+
161+ $ response ->assertSessionHasErrors ([
162+ 'idProveedor ' ,
163+ 'idCategoria ' ,
164+ 'idUnidadMedida ' ,
165+ 'nombreIngrediente ' ,
166+ 'referenciaIngrediente ' ,
167+ ]);
168+ }
169+
170+ #[Test]
171+ public function store_falla_si_referencia_supera_50_caracteres (): void
172+ {
173+ $ response = $ this ->post ('/inventario/ingredientes/store ' , [
174+ 'idProveedor ' => 1 ,
175+ 'idCategoria ' => 1 ,
176+ 'idUnidadMedida ' => 1 ,
177+ 'nombreIngrediente ' => 'Harina ' ,
178+ 'referenciaIngrediente ' => str_repeat ('X ' , 51 ), // 51 chars > max:50
179+ ]);
180+
181+ $ response ->assertSessionHasErrors (['referenciaIngrediente ' ]);
182+ }
183+
184+ #[Test]
185+ public function store_redirige_con_error_cuando_spring_rechaza (): void
186+ {
187+ Http::fake ([
188+ '*/crearingrediente ' => Http::response ('Error en Spring ' , 400 )
189+ ]);
190+
191+ $ response = $ this ->post ('/inventario/ingredientes/store ' , [
192+ 'idProveedor ' => 1 ,
193+ 'idCategoria ' => 1 ,
194+ 'idUnidadMedida ' => 1 ,
195+ 'nombreIngrediente ' => 'Harina ' ,
196+ 'referenciaIngrediente ' => 'HAR-001 ' ,
197+ ]);
198+
199+ $ response ->assertSessionHas ('error ' );
200+ $ response ->assertRedirect (); // vuelve atrás
201+ }
202+
203+ // =========================================================
204+ // update()
205+ // =========================================================
206+
207+ #[Test]
208+ public function update_con_datos_validos_redirige_con_success (): void
209+ {
210+ Http::fake ([
211+ '*/ingrediente/1 ' => Http::response ('Actualizado ' , 200 )
212+ ]);
213+
214+ $ response = $ this ->put ('/inventario/ingredientes/update/1 ' , [
215+ 'idProveedor ' => 1 ,
216+ 'idCategoria ' => 1 ,
217+ 'idUnidadMedida ' => 1 ,
218+ 'nombreIngrediente ' => 'Harina Premium ' ,
219+ 'referenciaIngrediente ' => 'HAR-002 ' ,
220+ ]);
221+
222+ $ response ->assertRedirect (route ('ingredientes.index ' ));
223+ $ response ->assertSessionHas ('success ' , 'Ingrediente actualizado con éxito. ' );
224+ }
225+
226+ #[Test]
227+ public function update_falla_validacion_con_datos_vacios (): void
228+ {
229+ $ response = $ this ->put ('/inventario/ingredientes/update/1 ' , []);
230+
231+ $ response ->assertSessionHasErrors ([
232+ 'idProveedor ' ,
233+ 'idCategoria ' ,
234+ 'idUnidadMedida ' ,
235+ 'nombreIngrediente ' ,
236+ 'referenciaIngrediente ' ,
237+ ]);
238+ }
239+
240+ // =========================================================
241+ // destroy()
242+ // =========================================================
243+
244+ #[Test]
245+ public function destroy_elimina_y_redirige_con_success (): void
246+ {
247+ Http::fake ([
248+ '*/ingrediente/1 ' => Http::response ('Eliminado ' , 200 )
249+ ]);
250+
251+ $ response = $ this ->delete ('/inventario/ingredientes/delete/1 ' );
252+
253+ $ response ->assertRedirect (route ('ingredientes.index ' ));
254+ $ response ->assertSessionHas ('success ' , 'Ingrediente eliminado con éxito. ' );
255+ }
256+
257+ #[Test]
258+ public function destroy_redirige_con_error_cuando_spring_falla (): void
259+ {
260+ Http::fake ([
261+ '*/ingrediente/1 ' => Http::response ('Error ' , 500 )
262+ ]);
263+
264+ $ response = $ this ->delete ('/inventario/ingredientes/delete/1 ' );
265+
266+ $ response ->assertSessionHas ('error ' );
267+ }
268+
269+ // =========================================================
270+ // ingresarStock() — responde JSON
271+ // =========================================================
272+
273+ #[Test]
274+ public function ingresar_stock_retorna_json_success_true (): void
275+ {
276+ Http::fake ([
277+ '*/ingredientes/1/ingreso ' => Http::response ('Stock actualizado ' , 200 )
278+ ]);
279+
280+ $ response = $ this ->post ('/ingredientes/1/ingresar-stock ' , [
281+ 'cantidadIngresada ' => 10.5
282+ ]);
283+
284+ $ response ->assertStatus (200 );
285+ $ response ->assertJson (['success ' => true ]);
286+ }
287+
288+ #[Test]
289+ public function ingresar_stock_falla_con_cantidad_cero (): void
290+ {
291+ $ response = $ this ->post ('/ingredientes/1/ingresar-stock ' , [
292+ 'cantidadIngresada ' => 0 // min:0.01
293+ ]);
294+
295+ $ response ->assertSessionHasErrors (['cantidadIngresada ' ]);
296+ }
297+
298+ #[Test]
299+ public function ingresar_stock_retorna_json_error_cuando_spring_falla (): void
300+ {
301+ Http::fake ([
302+ '*/ingredientes/1/ingreso ' => Http::response ('Error stock ' , 500 )
303+ ]);
304+
305+ $ response = $ this ->post ('/ingredientes/1/ingresar-stock ' , [
306+ 'cantidadIngresada ' => 10.0
307+ ]);
308+
309+ $ response ->assertStatus (500 );
310+ $ response ->assertJson (['success ' => false ]);
311+ }
312+ }
0 commit comments