1+ package fr .insee .genesis .infrastructure .adapter ;
2+
3+ import fr .insee .genesis .domain .model .extraction .json .LastJsonExtractionModel ;
4+ import fr .insee .genesis .domain .model .surveyunit .Mode ;
5+ import fr .insee .genesis .exceptions .GenesisException ;
6+ import fr .insee .genesis .infrastructure .document .extraction .json .LastJsonExtractionDocument ;
7+ import fr .insee .genesis .infrastructure .repository .LastJsonExtractionMongoDBRepository ;
8+ import org .junit .jupiter .api .DisplayName ;
9+ import org .junit .jupiter .api .Nested ;
10+ import org .junit .jupiter .api .Test ;
11+ import org .junit .jupiter .api .extension .ExtendWith ;
12+ import org .mockito .ArgumentCaptor ;
13+ import org .mockito .InjectMocks ;
14+ import org .mockito .Mock ;
15+ import org .mockito .junit .jupiter .MockitoExtension ;
16+
17+ import java .util .Optional ;
18+
19+ import static org .assertj .core .api .Assertions .assertThat ;
20+ import static org .assertj .core .api .Assertions .assertThatThrownBy ;
21+ import static org .mockito .ArgumentMatchers .any ;
22+ import static org .mockito .Mockito .verify ;
23+ import static org .mockito .Mockito .verifyNoMoreInteractions ;
24+ import static org .mockito .Mockito .when ;
25+
26+ @ ExtendWith (MockitoExtension .class )
27+ @ DisplayName ("LastJsonExtractionMongoAdapter tests" )
28+ class LastJsonExtractionMongoAdapterTest {
29+
30+ private static final String COLLECTION_INSTRUMENT_ID = "instrument-123" ;
31+ private static final Mode MODE = Mode .WEB ;
32+ // Expected id format: "{collectionInstrumentId}_{mode}"
33+ private static final String EXPECTED_ID = "instrument-123_WEB" ;
34+
35+ @ Mock
36+ private LastJsonExtractionMongoDBRepository extractionRepository ;
37+
38+ @ InjectMocks
39+ private LastJsonExtractionMongoAdapter adapter ;
40+
41+ @ Nested
42+ @ DisplayName ("save() tests" )
43+ class SaveTests {
44+
45+ @ Test
46+ @ DisplayName ("save() should call repository.save() with a non-null document" )
47+ void save_shouldCallRepositorySave () {
48+ //GIVEN
49+ LastJsonExtractionModel model = buildModel ();
50+
51+ //WHEN
52+ adapter .save (model );
53+
54+ //THEN
55+ verify (extractionRepository ).save (any (LastJsonExtractionDocument .class ));
56+ verifyNoMoreInteractions (extractionRepository );
57+ }
58+
59+ @ Test
60+ @ DisplayName ("save() should map the model to a document before persisting" )
61+ void save_shouldMapModelToDocument () {
62+ //GIVEN
63+ LastJsonExtractionModel model = buildModel ();
64+
65+ //WHEN
66+ adapter .save (model );
67+
68+ //THEN
69+ ArgumentCaptor <LastJsonExtractionDocument > captor = ArgumentCaptor .forClass (LastJsonExtractionDocument .class );
70+ verify (extractionRepository ).save (captor .capture ());
71+ assertThat (captor .getValue ()).isNotNull ();
72+ }
73+ }
74+
75+ @ Nested
76+ @ DisplayName ("getLastExecutionDate() tests" )
77+ class GetLastExecutionDateTests {
78+
79+ @ Test
80+ @ DisplayName ("Should return mapped model when document is found" )
81+ void getLastExecutionDate_found_shouldReturnModel () throws GenesisException {
82+ //GIVEN
83+ LastJsonExtractionDocument doc = new LastJsonExtractionDocument ();
84+ when (extractionRepository .findById (EXPECTED_ID )).thenReturn (Optional .of (doc ));
85+
86+ //WHEN
87+ LastJsonExtractionModel result = adapter .getLastExecutionDate (COLLECTION_INSTRUMENT_ID , MODE );
88+
89+ //THEN
90+ assertThat (result ).isNotNull ();
91+ verify (extractionRepository ).findById (EXPECTED_ID );
92+ }
93+
94+ @ Test
95+ @ DisplayName ("Should throw GenesisException with 404 status when document is not found" )
96+ void getLastExecutionDate_notFound_shouldThrow404 () {
97+ //GIVEN
98+ when (extractionRepository .findById (EXPECTED_ID )).thenReturn (Optional .empty ());
99+
100+ //WHEN + THEN
101+ assertThatThrownBy (() -> adapter .getLastExecutionDate (COLLECTION_INSTRUMENT_ID , MODE ))
102+ .isInstanceOf (GenesisException .class )
103+ .satisfies (ex -> {
104+ GenesisException genesisException = (GenesisException ) ex ;
105+ assertThat (genesisException .getStatus ()).isEqualTo (404 );
106+ });
107+ }
108+
109+ @ Test
110+ @ DisplayName ("Should include collectionInstrumentId in exception message when not found" )
111+ void getLastExecutionDate_notFound_exceptionMessageShouldContainInstrumentId () {
112+ //GIVEN
113+ when (extractionRepository .findById (EXPECTED_ID )).thenReturn (Optional .empty ());
114+
115+ //WHEN + THEN
116+ assertThatThrownBy (() -> adapter .getLastExecutionDate (COLLECTION_INSTRUMENT_ID , MODE ))
117+ .isInstanceOf (GenesisException .class )
118+ .hasMessageContaining (COLLECTION_INSTRUMENT_ID );
119+ }
120+
121+ @ Test
122+ @ DisplayName ("Should include mode name in exception message when not found" )
123+ void getLastExecutionDate_notFound_exceptionMessageShouldContainModeName () {
124+ //GIVEN
125+ when (extractionRepository .findById (EXPECTED_ID )).thenReturn (Optional .empty ());
126+
127+ //WHEN + THEN
128+ assertThatThrownBy (() -> adapter .getLastExecutionDate (COLLECTION_INSTRUMENT_ID , MODE ))
129+ .isInstanceOf (GenesisException .class )
130+ .hasMessageContaining (MODE .getModeName ());
131+ }
132+
133+ @ Test
134+ @ DisplayName ("Should query repository with id formatted as '{collectionInstrumentId}_{mode}'" )
135+ void getLastExecutionDate_shouldUseCorrectIdFormat () throws GenesisException {
136+ //GIVEN
137+ LastJsonExtractionDocument doc = new LastJsonExtractionDocument ();
138+ when (extractionRepository .findById (EXPECTED_ID )).thenReturn (Optional .of (doc ));
139+
140+ //WHEN
141+ adapter .getLastExecutionDate (COLLECTION_INSTRUMENT_ID , MODE );
142+
143+ //THEN
144+ ArgumentCaptor <String > captor = ArgumentCaptor .forClass (String .class );
145+ verify (extractionRepository ).findById (captor .capture ());
146+ assertThat (captor .getValue ()).isEqualTo (EXPECTED_ID );
147+ }
148+
149+ @ Test
150+ @ DisplayName ("Should handle null mode — id should be '{collectionInstrumentId}_null'" )
151+ void getLastExecutionDate_nullMode_shouldUseNullInId () {
152+ //GIVEN
153+ String expectedIdWithNullMode = "instrument-123_null" ;
154+ when (extractionRepository .findById (expectedIdWithNullMode )).thenReturn (Optional .empty ());
155+
156+ //WHEN + THEN
157+ assertThatThrownBy (() -> adapter .getLastExecutionDate (COLLECTION_INSTRUMENT_ID , null ))
158+ .isInstanceOf (GenesisException .class )
159+ .satisfies (ex -> assertThat (((GenesisException ) ex ).getStatus ()).isEqualTo (404 ));
160+
161+ verify (extractionRepository ).findById (expectedIdWithNullMode );
162+ }
163+
164+ @ Test
165+ @ DisplayName ("Exception message should contain 'null' for mode name when mode is null" )
166+ void getLastExecutionDate_nullMode_exceptionMessageShouldContainNull () {
167+ //GIVEN
168+ String expectedIdWithNullMode = "instrument-123_null" ;
169+ when (extractionRepository .findById (expectedIdWithNullMode )).thenReturn (Optional .empty ());
170+
171+ //WHEN + THEN
172+ assertThatThrownBy (() -> adapter .getLastExecutionDate (COLLECTION_INSTRUMENT_ID , null ))
173+ .isInstanceOf (GenesisException .class )
174+ .hasMessageContaining ("null" );
175+ }
176+ }
177+
178+ @ Nested
179+ @ DisplayName ("delete() tests" )
180+ class DeleteTests {
181+
182+ @ Test
183+ @ DisplayName ("delete() should call repository.deleteById() with the correct id" )
184+ void delete_shouldCallDeleteById () {
185+ //WHEN
186+ adapter .delete (COLLECTION_INSTRUMENT_ID , MODE );
187+
188+ //THEN
189+ ArgumentCaptor <String > captor = ArgumentCaptor .forClass (String .class );
190+ verify (extractionRepository ).deleteById (captor .capture ());
191+ assertThat (captor .getValue ()).isEqualTo (EXPECTED_ID );
192+ verifyNoMoreInteractions (extractionRepository );
193+ }
194+
195+ @ Test
196+ @ DisplayName ("delete() with null mode should use 'null' in the id" )
197+ void delete_nullMode_shouldUseNullInId () {
198+ //WHEN
199+ adapter .delete (COLLECTION_INSTRUMENT_ID , null );
200+
201+ //THEN
202+ ArgumentCaptor <String > captor = ArgumentCaptor .forClass (String .class );
203+ verify (extractionRepository ).deleteById (captor .capture ());
204+ assertThat (captor .getValue ()).isEqualTo ("instrument-123_null" );
205+ }
206+
207+ @ Test
208+ @ DisplayName ("delete() id format should match getLastExecutionDate() id format" )
209+ void delete_idFormatShouldMatchGetLastExecutionDate () throws GenesisException {
210+ //GIVEN
211+ // Ensure both methods build the id the same way
212+ LastJsonExtractionDocument doc = new LastJsonExtractionDocument ();
213+ when (extractionRepository .findById (any ())).thenReturn (Optional .of (doc ));
214+
215+ //WHEN
216+ adapter .getLastExecutionDate (COLLECTION_INSTRUMENT_ID , MODE );
217+ adapter .delete (COLLECTION_INSTRUMENT_ID , MODE );
218+
219+ //THEN
220+ ArgumentCaptor <String > findCaptor = ArgumentCaptor .forClass (String .class );
221+ ArgumentCaptor <String > deleteCaptor = ArgumentCaptor .forClass (String .class );
222+ verify (extractionRepository ).findById (findCaptor .capture ());
223+ verify (extractionRepository ).deleteById (deleteCaptor .capture ());
224+
225+ assertThat (findCaptor .getValue ()).isEqualTo (deleteCaptor .getValue ());
226+ }
227+
228+ @ Test
229+ @ DisplayName ("delete() should work for all known modes" )
230+ void delete_shouldWorkForAllModes () {
231+ for (Mode mode : Mode .values ()) {
232+ //GIVEN
233+ String expectedId = String .format ("%s_%s" , COLLECTION_INSTRUMENT_ID , mode );
234+
235+ //WHEN
236+ adapter .delete (COLLECTION_INSTRUMENT_ID , mode );
237+
238+ //THEN
239+ verify (extractionRepository ).deleteById (expectedId );
240+ }
241+ }
242+ }
243+
244+ // -------------------------------------------------------------------------
245+ // Helpers
246+ // -------------------------------------------------------------------------
247+
248+ private LastJsonExtractionModel buildModel () {
249+ return LastJsonExtractionModel .builder ()
250+ .collectionInstrumentId (COLLECTION_INSTRUMENT_ID )
251+ .mode (MODE )
252+ .build ();
253+ }
254+ }
0 commit comments