99import static java .util .concurrent .TimeUnit .SECONDS ;
1010import static org .hamcrest .MatcherAssert .assertThat ;
1111import static org .hamcrest .Matchers .containsString ;
12+ import static org .hamcrest .Matchers .endsWith ;
1213import static org .hamcrest .Matchers .greaterThanOrEqualTo ;
1314import static org .hamcrest .Matchers .not ;
1415import static org .hamcrest .Matchers .startsWith ;
1718import static org .springframework .security .test .web .servlet .request .SecurityMockMvcRequestPostProcessors .csrf ;
1819import static org .springframework .test .web .servlet .request .MockMvcRequestBuilders .get ;
1920import static org .springframework .test .web .servlet .request .MockMvcRequestBuilders .post ;
21+ import static org .springframework .test .web .servlet .result .MockMvcResultMatchers .jsonPath ;
2022import static org .springframework .test .web .servlet .result .MockMvcResultMatchers .request ;
2123import static org .springframework .test .web .servlet .result .MockMvcResultMatchers .status ;
2224import static org .tailormap .api .TestRequestProcessor .setServletPath ;
25+ import static org .tailormap .api .controller .LayerExtractController .ExtractOutputFormat .CSV ;
26+ import static org .tailormap .api .controller .LayerExtractController .ExtractOutputFormat .GEOJSON ;
2327import static org .tailormap .api .controller .TestUrls .layerBegroeidTerreindeelPostgis ;
2428import static org .tailormap .api .controller .TestUrls .layerProxiedWithAuthInPublicApp ;
2529
@@ -91,7 +95,7 @@ void should_export_large_filter_to_csv() throws Exception {
9195 .with (setServletPath (extractUrl ))
9296 .with (csrf ())
9397 .param ("attributes" , "" )
94- .param ("outputFormat" , "csv" )
98+ .param ("outputFormat" , CSV . getValue () )
9599 .param ("filter" , StaticTestData .get ("large_cql_filter" ))
96100 .acceptCharset (StandardCharsets .UTF_8 )
97101 .characterEncoding (StandardCharsets .UTF_8 )
@@ -115,7 +119,7 @@ void should_export_large_filter_to_csv() throws Exception {
115119 assertThat (lastCompletedEventJson .length (), greaterThanOrEqualTo (100 ));
116120
117121 final String extractedDownloadId = getDownloadId (lastCompletedEventJson );
118- assertThat (extractedDownloadId , containsString ( ".csv" ));
122+ assertThat (extractedDownloadId , endsWith ( CSV . getExtension () ));
119123
120124 final String downloadUrl = apiBasePath + layerBegroeidTerreindeelPostgis + downloadPath + extractedDownloadId ;
121125 MvcResult download = mockMvc .perform (get (downloadUrl ).with (setServletPath (downloadUrl )))
@@ -145,7 +149,7 @@ void should_export_large_output_to_csv() throws Exception {
145149 .with (setServletPath (extractUrl ))
146150 .with (csrf ())
147151 .param ("attributes" , "identificatie, class" )
148- .param ("outputFormat" , "csv" )
152+ .param ("outputFormat" , CSV . getValue () )
149153 .acceptCharset (StandardCharsets .UTF_8 )
150154 .characterEncoding (StandardCharsets .UTF_8 )
151155 .contentType (MediaType .APPLICATION_FORM_URLENCODED ))
@@ -168,7 +172,7 @@ void should_export_large_output_to_csv() throws Exception {
168172 assertThat (lastCompletedEventJson .length (), greaterThanOrEqualTo (100 ));
169173
170174 final String extractedDownloadId = getDownloadId (lastCompletedEventJson );
171- assertThat (extractedDownloadId , containsString ( ".csv" ));
175+ assertThat (extractedDownloadId , endsWith ( CSV . getExtension () ));
172176
173177 final String downloadUrl = apiBasePath + layerBegroeidTerreindeelPostgis + downloadPath + extractedDownloadId ;
174178 MvcResult download = mockMvc .perform (get (downloadUrl ).with (setServletPath (downloadUrl )))
@@ -210,7 +214,7 @@ void should_export_wfs_to_csv_with_authentication() throws Exception {
210214 .with (setServletPath (extractUrl ))
211215 .with (csrf ())
212216 .param ("attributes" , "geom,naam,code" )
213- .param ("outputFormat" , "csv" )
217+ .param ("outputFormat" , CSV . getValue () )
214218 .acceptCharset (StandardCharsets .UTF_8 )
215219 .characterEncoding (StandardCharsets .UTF_8 )
216220 .contentType (MediaType .APPLICATION_FORM_URLENCODED ))
@@ -233,7 +237,7 @@ void should_export_wfs_to_csv_with_authentication() throws Exception {
233237 assertThat (lastCompletedEventJson .length (), greaterThanOrEqualTo (100 ));
234238
235239 final String extractedDownloadId = getDownloadId (lastCompletedEventJson );
236- assertThat (extractedDownloadId , containsString ( ".csv" ));
240+ assertThat (extractedDownloadId , endsWith ( CSV . getExtension () ));
237241
238242 final String downloadUrl = apiBasePath + layerProxiedWithAuthInPublicApp + downloadPath + extractedDownloadId ;
239243 MvcResult download = mockMvc .perform (get (downloadUrl ).with (setServletPath (downloadUrl )))
@@ -347,6 +351,63 @@ void should_export_large_filter_to_excel() throws Exception {
347351 }
348352 }
349353
354+ @ Test
355+ void should_export_large_filter_to_geojson () throws Exception {
356+ final String extractUrl = apiBasePath + layerBegroeidTerreindeelPostgis + extractPath + sseClientId ;
357+ mockMvc .perform (post (extractUrl )
358+ .accept (MediaType .APPLICATION_JSON )
359+ .with (setServletPath (extractUrl ))
360+ .with (csrf ())
361+ .param ("attributes" , "" )
362+ .param ("outputFormat" , GEOJSON .getValue ())
363+ .param ("filter" , StaticTestData .get ("large_cql_filter" ))
364+ .acceptCharset (StandardCharsets .UTF_8 )
365+ .characterEncoding (StandardCharsets .UTF_8 )
366+ .contentType (MediaType .APPLICATION_FORM_URLENCODED ))
367+ .andExpect (status ().isAccepted ());
368+
369+ // The SseEventBus may dispatch events slightly after the POST returns.
370+ // Awaitility polls the buffered SSE response until the expected content appears.
371+ Awaitility .await ()
372+ .atMost (10 , SECONDS )
373+ .untilAsserted (() -> assertThat (
374+ sseResult .getResponse ().getContentAsString (), containsString ("Extract task received" )));
375+
376+ Awaitility .await ().pollInterval (5 , SECONDS ).atMost (30 , SECONDS ).untilAsserted (() -> {
377+ final String stream = sseResult .getResponse ().getContentAsString ();
378+ assertThat (count_completed_messages (stream ), greaterThanOrEqualTo (1 ));
379+ });
380+
381+ final String lastCompletedEventJson =
382+ getLastCompletedEventJson (sseResult .getResponse ().getContentAsString ());
383+ assertThat (lastCompletedEventJson .length (), greaterThanOrEqualTo (100 ));
384+
385+ final String extractedDownloadId = getDownloadId (lastCompletedEventJson );
386+ assertThat (extractedDownloadId , endsWith (GEOJSON .getExtension ()));
387+
388+ final String downloadUrl = apiBasePath + layerBegroeidTerreindeelPostgis + downloadPath + extractedDownloadId ;
389+ mockMvc .perform (get (downloadUrl ).with (setServletPath (downloadUrl )))
390+ .andExpect (status ().isOk ())
391+ .andExpect (result -> {
392+ String contentType = result .getResponse ().getContentType ();
393+ assertThat (contentType , containsString ("application/geo+json" ));
394+
395+ String contentDisposition = result .getResponse ().getHeader ("Content-Disposition" );
396+ assertThat (contentDisposition , containsString ("attachment; filename=" ));
397+ assertThat (contentDisposition , containsString (extractedDownloadId ));
398+ })
399+ .andExpect (jsonPath ("$.type" ).value ("FeatureCollection" ))
400+ .andExpect (jsonPath ("$.features.length()" ).value (18 ))
401+ .andExpect (jsonPath ("$.features[0].type" ).value ("Feature" ))
402+ .andExpect (jsonPath ("$.features[0].geometry" ).isNotEmpty ())
403+ .andExpect (jsonPath ("$.features[0].properties.length()" ).value (13 ))
404+ .andExpect (jsonPath ("$.features[0].properties.bronhouder" ).value ("G0344" ))
405+ .andExpect (jsonPath ("$.features[0].geometry.type" ).value ("Polygon" ))
406+ // no CRS members
407+ .andExpect (jsonPath ("$.crs" ).doesNotHaveJsonPath ())
408+ .andExpect (jsonPath ("$.features[0].crs" ).doesNotHaveJsonPath ());
409+ }
410+
350411 /**
351412 * Parse the last non-empty line from the SSE stream that looks something like:
352413 * {@code data:{"details":{"message":"Extract task
0 commit comments