Skip to content

Commit cdf8c7c

Browse files
committed
feat: Add comprehensive Search API tests for product and category search functionality
1 parent 6b12ec2 commit cdf8c7c

File tree

1 file changed

+286
-0
lines changed

1 file changed

+286
-0
lines changed
Lines changed: 286 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,286 @@
1+
<?php
2+
3+
namespace Tests\Feature;
4+
5+
use App\Models\Product;
6+
use App\Models\Category;
7+
use App\Models\User;
8+
use Illuminate\Foundation\Testing\RefreshDatabase;
9+
use Tests\TestCase;
10+
11+
class SearchApiTest extends TestCase
12+
{
13+
use RefreshDatabase;
14+
15+
protected $user;
16+
protected $electronicsCategory;
17+
protected $booksCategory;
18+
protected $laptop;
19+
protected $phone;
20+
protected $novel;
21+
22+
protected function setUp(): void
23+
{
24+
parent::setUp();
25+
26+
$this->user = User::factory()->create();
27+
28+
// Create test categories
29+
$this->electronicsCategory = Category::factory()->create([
30+
'name' => 'Electronics',
31+
'slug' => 'electronics',
32+
]);
33+
34+
$this->booksCategory = Category::factory()->create([
35+
'name' => 'Books',
36+
'slug' => 'books',
37+
]);
38+
39+
// Create test products
40+
$this->laptop = Product::factory()->create([
41+
'name' => 'Gaming Laptop',
42+
'slug' => 'gaming-laptop',
43+
'description' => 'High-performance gaming laptop with RGB keyboard',
44+
'price' => 1299.99,
45+
'is_active' => true,
46+
'category_id' => $this->electronicsCategory->id,
47+
]);
48+
49+
$this->phone = Product::factory()->create([
50+
'name' => 'Smartphone Pro',
51+
'slug' => 'smartphone-pro',
52+
'description' => 'Latest smartphone with advanced camera features',
53+
'price' => 899.99,
54+
'is_active' => true,
55+
'category_id' => $this->electronicsCategory->id,
56+
]);
57+
58+
$this->novel = Product::factory()->create([
59+
'name' => 'Science Fiction Novel',
60+
'slug' => 'science-fiction-novel',
61+
'description' => 'Epic space adventure novel',
62+
'price' => 19.99,
63+
'is_active' => true,
64+
'category_id' => $this->booksCategory->id,
65+
]);
66+
}
67+
68+
/** @test */
69+
public function it_can_search_products_by_name()
70+
{
71+
$response = $this->getJson('/api/search?q=laptop');
72+
73+
$response->assertStatus(200)
74+
->assertJson([
75+
'success' => true,
76+
])
77+
->assertJsonStructure([
78+
'success',
79+
'data' => [
80+
'products' => [
81+
'*' => [
82+
'id',
83+
'name',
84+
'slug',
85+
'price',
86+
'category'
87+
]
88+
],
89+
'categories' => [
90+
'*' => [
91+
'id',
92+
'name',
93+
'slug'
94+
]
95+
],
96+
'total',
97+
'per_page',
98+
'current_page',
99+
'last_page'
100+
],
101+
'message'
102+
]);
103+
104+
$products = $response->json('data.products');
105+
$this->assertGreaterThan(0, count($products));
106+
$this->assertStringContainsString('Laptop', $products[0]['name']);
107+
}
108+
109+
/** @test */
110+
public function it_can_search_products_by_category()
111+
{
112+
$response = $this->getJson('/api/search?category=electronics');
113+
114+
$response->assertStatus(200);
115+
116+
$products = $response->json('data.products');
117+
foreach ($products as $product) {
118+
$this->assertEquals('electronics', $product['category']['slug']);
119+
}
120+
}
121+
122+
/** @test */
123+
public function it_can_filter_search_by_price_range()
124+
{
125+
$response = $this->getJson('/api/search?min_price=800&max_price=1000');
126+
127+
$response->assertStatus(200);
128+
129+
$products = $response->json('data.products');
130+
foreach ($products as $product) {
131+
$this->assertGreaterThanOrEqual(800, $product['price']);
132+
$this->assertLessThanOrEqual(1000, $product['price']);
133+
}
134+
}
135+
136+
/** @test */
137+
public function it_returns_empty_results_for_no_matches()
138+
{
139+
$response = $this->getJson('/api/search?q=nonexistentproduct');
140+
141+
$response->assertStatus(200);
142+
143+
$products = $response->json('data.products');
144+
$this->assertCount(0, $products);
145+
}
146+
147+
/** @test */
148+
public function it_validates_search_query_length()
149+
{
150+
$response = $this->getJson('/api/search?q=a');
151+
152+
$response->assertStatus(422)
153+
->assertJsonValidationErrors(['q']);
154+
}
155+
156+
/** @test */
157+
public function it_validates_price_range_parameters()
158+
{
159+
$response = $this->getJson('/api/search?min_price=-10');
160+
161+
$response->assertStatus(422)
162+
->assertJsonValidationErrors(['min_price']);
163+
}
164+
165+
/** @test */
166+
public function it_can_search_categories()
167+
{
168+
$response = $this->getJson('/api/search?q=electro');
169+
170+
$response->assertStatus(200);
171+
172+
$categories = $response->json('data.categories');
173+
$this->assertGreaterThan(0, count($categories));
174+
175+
$electronicsFound = false;
176+
foreach ($categories as $category) {
177+
if ($category['slug'] === 'electronics') {
178+
$electronicsFound = true;
179+
break;
180+
}
181+
}
182+
$this->assertTrue($electronicsFound);
183+
}
184+
185+
/** @test */
186+
public function it_searches_in_category_names_and_descriptions()
187+
{
188+
$response = $this->getJson('/api/search?q=books');
189+
190+
$response->assertStatus(200);
191+
192+
$categories = $response->json('data.categories');
193+
$this->assertGreaterThan(0, count($categories));
194+
}
195+
196+
/** @test */
197+
public function it_paginates_search_results_correctly()
198+
{
199+
// Create more products for pagination testing
200+
for ($i = 0; $i < 25; $i++) {
201+
Product::factory()->create([
202+
'name' => "Product {$i}",
203+
'price' => 10.00 + $i,
204+
'is_active' => true,
205+
'category_id' => $this->electronicsCategory->id,
206+
]);
207+
}
208+
209+
$response = $this->getJson('/api/search?q=Product&per_page=10&page=1');
210+
211+
$response->assertStatus(200);
212+
213+
$data = $response->json('data');
214+
$this->assertEquals(10, $data['per_page']);
215+
$this->assertEquals(1, $data['current_page']);
216+
$this->assertLessThanOrEqual(10, count($data['products']));
217+
}
218+
219+
/** @test */
220+
public function it_handles_page_parameter_correctly()
221+
{
222+
$response = $this->getJson('/api/search?q=Product&per_page=5&page=2');
223+
224+
$response->assertStatus(200);
225+
226+
$data = $response->json('data');
227+
$this->assertEquals(2, $data['current_page']);
228+
$this->assertEquals(5, $data['per_page']);
229+
}
230+
231+
/** @test */
232+
public function it_includes_search_metadata_in_response()
233+
{
234+
$response = $this->getJson('/api/search?q=laptop');
235+
236+
$response->assertStatus(200);
237+
238+
$data = $response->json('data');
239+
$this->assertArrayHasKey('total', $data);
240+
$this->assertArrayHasKey('per_page', $data);
241+
$this->assertArrayHasKey('current_page', $data);
242+
$this->assertArrayHasKey('last_page', $data);
243+
}
244+
245+
/** @test */
246+
public function it_handles_large_result_sets_efficiently()
247+
{
248+
// Create many products
249+
for ($i = 0; $i < 100; $i++) {
250+
Product::factory()->create([
251+
'name' => "Searchable Product {$i}",
252+
'is_active' => true,
253+
'category_id' => $this->electronicsCategory->id,
254+
]);
255+
}
256+
257+
$startTime = microtime(true);
258+
$response = $this->getJson('/api/search?q=Searchable');
259+
$endTime = microtime(true);
260+
261+
$response->assertStatus(200);
262+
$responseTime = ($endTime - $startTime) * 1000; // Convert to milliseconds
263+
264+
// Should respond within reasonable time (less than 1 second)
265+
$this->assertLessThan(1000, $responseTime);
266+
}
267+
268+
/** @test */
269+
public function it_handles_malformed_search_requests_gracefully()
270+
{
271+
$response = $this->getJson('/api/search');
272+
273+
// Should either return empty results or require query parameter
274+
$this->assertContains($response->status(), [200, 422]);
275+
}
276+
277+
/** @test */
278+
public function it_handles_database_connection_issues_gracefully()
279+
{
280+
// This would require mocking database failures
281+
// For now, we'll test with invalid parameters
282+
$response = $this->getJson('/api/search?q=' . str_repeat('a', 1000));
283+
284+
$response->assertStatus(422);
285+
}
286+
}

0 commit comments

Comments
 (0)