11package edu .harvard .iq .dataverse .api ;
22
33import io .restassured .RestAssured ;
4- import io .restassured .response .Response ;
5- import java .util .Arrays ;
6- import java .util .HashSet ;
7- import java .util .List ;
8- import java .util .Locale ;
9- import java .util .Set ;
10- import java .util .stream .Collectors ;
114import org .junit .jupiter .api .BeforeAll ;
125import org .junit .jupiter .params .ParameterizedTest ;
136import org .junit .jupiter .params .provider .ValueSource ;
1710import static org .hamcrest .Matchers .blankOrNullString ;
1811import static org .hamcrest .Matchers .is ;
1912import static org .hamcrest .Matchers .not ;
20- import static org .junit .jupiter .api .Assertions .assertEquals ;
21- import static org .junit .jupiter .api .Assertions .assertTrue ;
2213
2314/**
2415 * Integration tests for CORS headers on API endpoints. These tests verify that the expected CORS
3021 * env to `dev_dataverse`.
3122 */
3223class CorsIT {
33- private static final String ORIGIN_NULL = "null" ;
34-
35- private final List <String > expectedCorsMethods = List .of ("GET" , "POST" , "PUT" , "DELETE" , "OPTIONS" );
36- private final List <String > expectedCorsAllowHeaders = List .of ("Accept" , "Content-Type" , "X-Dataverse-key" , "Range" );
37- private final List <String > expectedCorsExposeHeaders = List .of ("Accept-Ranges" , "Content-Range" , "Content-Encoding" );
38-
3924 @ BeforeAll
4025 static void setUp () {
4126 RestAssured .baseURI = UtilIT .getRestAssuredBaseUri ();
4227 }
43-
28+
29+ /**
30+ * Tests the presence of CORS preflight headers on various subsystems by sending HTTP OPTIONS requests
31+ * to specified paths and validating the responses for expected headers and status.
32+ * These paths are served by different servlets, filters, and frameworks.
33+ * Nonetheless, any of them should present CORS headers when configured.
34+ *
35+ * <p>
36+ * TODO: Currently, this test relies on the CI infrastructure executing the test to have set at least
37+ * the JVM setting dataverse.cors.origin. Otherwise, no headers will be sent.
38+ * </p>
39+ *
40+ * <p>
41+ * NOTE: At the time of writing (2026-04), there is no infrastructure available to a) manipulate
42+ * these settings in this end-to-end testing scenario nor b) to dynamically reload the test subject.
43+ * It is initialized once at deployment time, which would require isolating this test some other way.
44+ * Thus, only the <i>presence</i> of headers is checked, but not its content
45+ * (which is fine, given the scope of the test).
46+ * </p>
47+ *
48+ * @param path the relative path on the subsystem to which the CORS preflight request is sent
49+ */
4450 @ ParameterizedTest (name = "CORS preflight headers on {0}" )
4551 @ ValueSource (strings = {
4652 "/api/dataverses/root/datasets" ,
4753 "/api/v1/dataverses/root/datasets" ,
4854 "/page_doesnt_exist" ,
4955 "/dvn/api/data-deposit/v1.1/swordv2/collection/dataverse/root"
5056 })
51- void testPreflightOptionsCorsHeaders (String path ) {
52- Response response =
53- given ()
54- .header ("Accept" , "*/*" )
55- .header ("Accept-Language" , "en-US,en;q=0.9,es;q=0.8,hu;q=0.7" )
56- .header ("Access-Control-Request-Headers" , "content-type,x-dataverse-key" )
57- .header ("Access-Control-Request-Method" , "POST" )
58- .header ("Origin" , ORIGIN_NULL )
59- .header ("User-Agent" , "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36" )
60- .when ()
61- .options (path )
62- .then ()
63- .log ().ifValidationFails ()
64- .statusCode (anyOf (is (200 ), is (204 )))
65- .header ("Access-Control-Allow-Methods" , not (blankOrNullString ()))
66- .header ("Access-Control-Allow-Headers" , not (blankOrNullString ()))
67- .header ("Access-Control-Expose-Headers" , not (blankOrNullString ()))
68- .extract ()
69- .response ();
70-
71- assertHeaderSetEquals ("Access-Control-Allow-Methods" , expectedCorsMethods , response );
72- assertHeaderSetEquals ("Access-Control-Allow-Headers" , expectedCorsAllowHeaders , response );
73- assertHeaderSetEquals ("Access-Control-Expose-Headers" , expectedCorsExposeHeaders , response );
57+ void ensurePresenceOnDifferentSubsystems (String path ) {
58+ given ()
59+ .header ("Accept" , "*/*" )
60+ .header ("Accept-Language" , "en-US,en;q=0.9,es;q=0.8,hu;q=0.7" )
61+ .header ("Access-Control-Request-Headers" , "content-type,x-dataverse-key" )
62+ .header ("Access-Control-Request-Method" , "POST" )
63+ .header ("Origin" , "null" )
64+ .header ("User-Agent" , "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36" )
65+ .when ()
66+ .options (path )
67+ .then ()
68+ .log ().ifValidationFails ()
69+ .statusCode (anyOf (is (200 ), is (204 )))
70+ .header ("Access-Control-Allow-Methods" , not (blankOrNullString ()))
71+ .header ("Access-Control-Allow-Headers" , not (blankOrNullString ()))
72+ .header ("Access-Control-Expose-Headers" , not (blankOrNullString ()));
7473 }
74+
75+ /*
76+ The following code may be used in a future test enabling assertions of header contents:
77+
78+ Usage:
79+ assertHeaderSetEquals("Access-Control-Allow-Methods", expectedCorsMethods, response);
80+ assertHeaderSetEquals("Access-Control-Allow-Headers", expectedCorsAllowHeaders, response);
81+ assertHeaderSetEquals("Access-Control-Expose-Headers", expectedCorsExposeHeaders, response);
82+
83+ Class fields:
84+ private final List<String> expectedCorsMethods = List.of("GET", "POST", "PUT", "DELETE", "OPTIONS");
85+ private final List<String> expectedCorsAllowHeaders = List.of("Accept", "Content-Type", "X-Dataverse-key", "Range");
86+ private final List<String> expectedCorsExposeHeaders = List.of("Accept-Ranges", "Content-Range", "Content-Encoding");
7587
88+ Assertions methods:
7689 private static void assertHeaderSetEquals(String headerName, List<String> expectedTokens, Response response) {
7790 String headerValue = response.getHeader(headerName);
7891 assertTrue(headerValue != null && !headerValue.isBlank(), "Missing header: " + headerName);
@@ -93,4 +106,6 @@ private static Set<String> normalizeTokens(String headerValue) {
93106 private static String normalizeToken(String value) {
94107 return value == null ? "" : value.trim().toLowerCase(Locale.ROOT);
95108 }
109+
110+ */
96111}
0 commit comments