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