Skip to content

Commit 22e3b50

Browse files
committed
🧪 test: Add comprehensive CRUD system tests - CrudProcessor tests for annotation processing - CrudControllerWrapper tests for endpoint generation - CrudAnnotationIntegration tests for full workflow - ApiResponse tests for standardized responses
1 parent 0414ba1 commit 22e3b50

File tree

4 files changed

+1185
-0
lines changed

4 files changed

+1185
-0
lines changed
Lines changed: 300 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,300 @@
1+
package jazzyframework.data;
2+
3+
import jazzyframework.data.annotations.Crud;
4+
import jazzyframework.data.annotations.CrudOverride;
5+
import jazzyframework.di.DIContainer;
6+
import jazzyframework.di.annotations.Component;
7+
import jazzyframework.http.ApiResponse;
8+
import jazzyframework.http.Request;
9+
import jazzyframework.http.Response;
10+
import jazzyframework.routing.Router;
11+
import org.junit.jupiter.api.BeforeEach;
12+
import org.junit.jupiter.api.Test;
13+
import org.junit.jupiter.api.DisplayName;
14+
15+
import jakarta.persistence.Entity;
16+
import jakarta.persistence.Id;
17+
import java.lang.reflect.Constructor;
18+
import java.util.*;
19+
20+
import static org.junit.jupiter.api.Assertions.*;
21+
22+
/**
23+
* Integration tests for @Crud annotation functionality.
24+
* Tests the complete CRUD annotation workflow focusing on annotation processing.
25+
*/
26+
class CrudAnnotationIntegrationTest {
27+
28+
private Router router;
29+
private DIContainer diContainer;
30+
private CrudProcessor crudProcessor;
31+
32+
// Test entity
33+
@Entity
34+
static class TestProduct {
35+
@Id
36+
private Long id;
37+
private String name;
38+
private String description;
39+
private Double price;
40+
private Integer stock;
41+
42+
public TestProduct() {}
43+
44+
public TestProduct(Long id, String name, String description, Double price, Integer stock) {
45+
this.id = id;
46+
this.name = name;
47+
this.description = description;
48+
this.price = price;
49+
this.stock = stock;
50+
}
51+
52+
// Getters and setters
53+
public Long getId() { return id; }
54+
public void setId(Long id) { this.id = id; }
55+
public String getName() { return name; }
56+
public void setName(String name) { this.name = name; }
57+
public String getDescription() { return description; }
58+
public void setDescription(String description) { this.description = description; }
59+
public Double getPrice() { return price; }
60+
public void setPrice(Double price) { this.price = price; }
61+
public Integer getStock() { return stock; }
62+
public void setStock(Integer stock) { this.stock = stock; }
63+
}
64+
65+
// Test repository interface
66+
interface TestProductRepository extends BaseRepository<TestProduct, Long> {
67+
}
68+
69+
// Full-featured CRUD controller
70+
@Component
71+
@Crud(
72+
entity = TestProduct.class,
73+
endpoint = "/api/products",
74+
enablePagination = true,
75+
enableSearch = true,
76+
searchableFields = {"name", "description"},
77+
enableBatchOperations = true,
78+
enableCount = true,
79+
enableExists = true,
80+
defaultPageSize = 20,
81+
maxPageSize = 100,
82+
maxBatchSize = 50
83+
)
84+
static class FullCrudController {
85+
private final TestProductRepository repository;
86+
87+
public FullCrudController(TestProductRepository repository) {
88+
this.repository = repository;
89+
}
90+
91+
// Override findAll to add custom logic
92+
@CrudOverride(value = "Custom findAll with stock filtering", operation = "findAll")
93+
public Response findAll(Request request) {
94+
return Response.json(ApiResponse.success("Custom findAll executed",
95+
Collections.singletonList(new TestProduct(1L, "Test Product", "Test Description", 99.99, 10))));
96+
}
97+
}
98+
99+
// Minimal CRUD controller
100+
@Component
101+
@Crud(entity = TestProduct.class, endpoint = "/api/simple-products")
102+
static class MinimalCrudController {
103+
private final TestProductRepository repository;
104+
105+
public MinimalCrudController(TestProductRepository repository) {
106+
this.repository = repository;
107+
}
108+
}
109+
110+
// Non-CRUD controller
111+
@Component
112+
static class RegularController {
113+
public Response getHealth(Request request) {
114+
return Response.json(ApiResponse.success("Healthy", null));
115+
}
116+
}
117+
118+
@BeforeEach
119+
void setUp() {
120+
router = new Router();
121+
diContainer = new DIContainer();
122+
crudProcessor = new CrudProcessor(router, diContainer);
123+
}
124+
125+
@Test
126+
@DisplayName("Should detect @Crud annotation on controllers")
127+
void shouldDetectCrudAnnotation() {
128+
// Test that @Crud annotation is properly detected
129+
assertTrue(FullCrudController.class.isAnnotationPresent(Crud.class));
130+
assertTrue(MinimalCrudController.class.isAnnotationPresent(Crud.class));
131+
assertFalse(RegularController.class.isAnnotationPresent(Crud.class));
132+
}
133+
134+
@Test
135+
@DisplayName("Should extract @Crud configuration correctly")
136+
void shouldExtractCrudConfiguration() {
137+
Crud fullConfig = FullCrudController.class.getAnnotation(Crud.class);
138+
139+
// Verify configuration values
140+
assertEquals(TestProduct.class, fullConfig.entity());
141+
assertEquals("/api/products", fullConfig.endpoint());
142+
assertTrue(fullConfig.enablePagination());
143+
assertTrue(fullConfig.enableSearch());
144+
assertTrue(fullConfig.enableBatchOperations());
145+
assertTrue(fullConfig.enableCount());
146+
assertTrue(fullConfig.enableExists());
147+
assertEquals(20, fullConfig.defaultPageSize());
148+
assertEquals(100, fullConfig.maxPageSize());
149+
assertEquals(50, fullConfig.maxBatchSize());
150+
assertArrayEquals(new String[]{"name", "description"}, fullConfig.searchableFields());
151+
}
152+
153+
@Test
154+
@DisplayName("Should extract minimal @Crud configuration with defaults")
155+
void shouldExtractMinimalCrudConfiguration() {
156+
Crud minimalConfig = MinimalCrudController.class.getAnnotation(Crud.class);
157+
158+
// Verify configuration values with defaults
159+
assertEquals(TestProduct.class, minimalConfig.entity());
160+
assertEquals("/api/simple-products", minimalConfig.endpoint());
161+
assertFalse(minimalConfig.enablePagination()); // Default is false
162+
assertFalse(minimalConfig.enableSearch()); // Default is false
163+
assertFalse(minimalConfig.enableBatchOperations()); // Default is false
164+
assertEquals(20, minimalConfig.defaultPageSize()); // Default value is 20, not 10
165+
assertEquals(100, minimalConfig.maxPageSize()); // Default value
166+
}
167+
168+
@Test
169+
@DisplayName("Should detect method overrides correctly")
170+
void shouldDetectMethodOverrides() {
171+
// Check if FullCrudController has findAll method override
172+
boolean hasFindAll = false;
173+
try {
174+
FullCrudController.class.getDeclaredMethod("findAll", Request.class);
175+
hasFindAll = true;
176+
} catch (NoSuchMethodException e) {
177+
// Method not found
178+
}
179+
180+
assertTrue(hasFindAll, "FullCrudController should have findAll method override");
181+
182+
// Check if MinimalCrudController doesn't have overrides
183+
boolean hasMinimalFindAll = false;
184+
try {
185+
MinimalCrudController.class.getDeclaredMethod("findAll", Request.class);
186+
hasMinimalFindAll = true;
187+
} catch (NoSuchMethodException e) {
188+
// Method not found - this is expected
189+
}
190+
191+
assertFalse(hasMinimalFindAll, "MinimalCrudController should not have findAll method override");
192+
}
193+
194+
@Test
195+
@DisplayName("Should handle @CrudOverride annotation correctly")
196+
void shouldHandleCrudOverrideAnnotation() {
197+
try {
198+
java.lang.reflect.Method findAllMethod = FullCrudController.class.getDeclaredMethod("findAll", Request.class);
199+
200+
// Check if method has @CrudOverride annotation
201+
boolean hasOverrideAnnotation = findAllMethod.isAnnotationPresent(CrudOverride.class);
202+
assertTrue(hasOverrideAnnotation, "Overridden method should have @CrudOverride annotation");
203+
204+
if (hasOverrideAnnotation) {
205+
CrudOverride overrideAnnotation = findAllMethod.getAnnotation(CrudOverride.class);
206+
assertEquals("Custom findAll with stock filtering", overrideAnnotation.value());
207+
assertEquals("findAll", overrideAnnotation.operation());
208+
}
209+
} catch (NoSuchMethodException e) {
210+
fail("findAll method should exist in FullCrudController");
211+
}
212+
}
213+
214+
@Test
215+
@DisplayName("Should validate CRUD annotation parameters")
216+
void shouldValidateCrudAnnotationParameters() {
217+
Crud config = FullCrudController.class.getAnnotation(Crud.class);
218+
219+
// Validate required parameters
220+
assertNotNull(config.entity(), "Entity class should not be null");
221+
assertNotNull(config.endpoint(), "Endpoint should not be null");
222+
assertFalse(config.endpoint().trim().isEmpty(), "Endpoint should not be empty");
223+
224+
// Validate optional parameters have reasonable defaults
225+
assertTrue(config.defaultPageSize() > 0, "Default page size should be positive");
226+
assertTrue(config.maxPageSize() > 0, "Max page size should be positive");
227+
assertTrue(config.maxBatchSize() > 0, "Max batch size should be positive");
228+
assertTrue(config.maxPageSize() >= config.defaultPageSize(),
229+
"Max page size should be >= default page size");
230+
}
231+
232+
@Test
233+
@DisplayName("Should create endpoint paths correctly")
234+
void shouldCreateEndpointPathsCorrectly() {
235+
Crud fullConfig = FullCrudController.class.getAnnotation(Crud.class);
236+
Crud minimalConfig = MinimalCrudController.class.getAnnotation(Crud.class);
237+
238+
// Verify endpoint paths
239+
assertEquals("/api/products", fullConfig.endpoint());
240+
assertEquals("/api/simple-products", minimalConfig.endpoint());
241+
242+
// Verify ID parameter name
243+
assertEquals("id", fullConfig.idParam()); // Default value
244+
assertEquals("id", minimalConfig.idParam()); // Default value
245+
}
246+
247+
@Test
248+
@DisplayName("Should validate all annotation properties")
249+
void shouldValidateAllAnnotationProperties() {
250+
Crud config = FullCrudController.class.getAnnotation(Crud.class);
251+
252+
// Test all string array properties
253+
assertTrue(config.searchableFields().length > 0, "Searchable fields should not be empty when search is enabled");
254+
assertTrue(config.excludeFields().length == 0, "Excluded fields should be empty by default");
255+
256+
// Test boolean properties
257+
assertTrue(config.enablePagination());
258+
assertTrue(config.enableSearch());
259+
assertTrue(config.enableBatchOperations());
260+
assertTrue(config.enableCount());
261+
assertTrue(config.enableExists());
262+
assertFalse(config.softDelete()); // Default is false
263+
assertTrue(config.enableValidation()); // Default is true
264+
assertFalse(config.enableAuditLog()); // Default is false
265+
266+
// Test numeric properties
267+
assertEquals(20, config.defaultPageSize());
268+
assertEquals(100, config.maxPageSize());
269+
assertEquals(50, config.maxBatchSize());
270+
271+
// Test string properties
272+
assertEquals("/api/products", config.endpoint());
273+
assertEquals("id", config.idParam());
274+
assertEquals("deletedAt", config.softDeleteField());
275+
assertEquals(ApiResponse.class, config.responseWrapper());
276+
}
277+
278+
@Test
279+
@DisplayName("Should handle controller class hierarchy")
280+
void shouldHandleControllerClassHierarchy() {
281+
// Test that we can properly inspect controller classes
282+
assertNotNull(FullCrudController.class.getConstructors());
283+
assertTrue(FullCrudController.class.getConstructors().length > 0);
284+
285+
// Verify class has proper @Component annotation
286+
assertTrue(FullCrudController.class.isAnnotationPresent(Component.class));
287+
288+
// Verify class has proper repository dependency
289+
Constructor<?>[] constructors = FullCrudController.class.getConstructors();
290+
boolean hasRepositoryDependency = false;
291+
for (Constructor<?> constructor : constructors) {
292+
if (constructor.getParameterTypes().length > 0 &&
293+
BaseRepository.class.isAssignableFrom(constructor.getParameterTypes()[0])) {
294+
hasRepositoryDependency = true;
295+
break;
296+
}
297+
}
298+
assertTrue(hasRepositoryDependency, "Controller should have repository dependency");
299+
}
300+
}

0 commit comments

Comments
 (0)