99import io .restassured .RestAssured ;
1010import io .restassured .response .Response ;
1111
12+ import java .nio .charset .StandardCharsets ;
1213import java .util .*;
1314import java .util .logging .Logger ;
1415
1516import edu .harvard .iq .dataverse .api .auth .ApiKeyAuthMechanism ;
1617import jakarta .json .JsonObject ;
18+ import jakarta .ws .rs .core .Response .Status ;
1719import org .assertj .core .util .Lists ;
20+ import org .hamcrest .Matcher ;
21+ import org .junit .jupiter .api .Nested ;
1822import org .junit .jupiter .api .Test ;
1923import org .junit .jupiter .api .BeforeAll ;
2024import io .restassured .path .json .JsonPath ;
2125
2226import static edu .harvard .iq .dataverse .api .ApiConstants .*;
27+ import static edu .harvard .iq .dataverse .settings .SettingsServiceBean .Key ;
2328import static io .restassured .path .json .JsonPath .with ;
2429import io .restassured .path .xml .XmlPath ;
2530import edu .harvard .iq .dataverse .settings .SettingsServiceBean ;
4550import org .hamcrest .CoreMatchers ;
4651import org .hamcrest .Matchers ;
4752import org .junit .jupiter .api .AfterAll ;
53+ import org .junit .jupiter .api .io .TempDir ;
54+ import org .junit .jupiter .params .ParameterizedTest ;
55+ import org .junit .jupiter .params .provider .Arguments ;
56+ import org .junit .jupiter .params .provider .MethodSource ;
4857
4958import static org .hamcrest .CoreMatchers .*;
5059import static org .junit .jupiter .api .Assertions .*;
@@ -59,15 +68,11 @@ public static void setUpClass() {
5968
6069 Response removePublicInstall = UtilIT .deleteSetting (SettingsServiceBean .Key .PublicInstall );
6170 removePublicInstall .then ().assertThat ().statusCode (200 );
62-
63- Response removeLimit = UtilIT .deleteSetting (SettingsServiceBean .Key .TabularIngestSizeLimit );
64- removeLimit .then ().assertThat ().statusCode (OK .getStatusCode ());
6571 }
6672
6773 @ AfterAll
6874 public static void tearDownClass () {
6975 UtilIT .deleteSetting (SettingsServiceBean .Key .PublicInstall );
70- UtilIT .deleteSetting (SettingsServiceBean .Key .TabularIngestSizeLimit );
7176 }
7277
7378 /**
@@ -1211,183 +1216,102 @@ public void test_AddFileBadUploadFormat() {
12111216 }
12121217
12131218 }
1214-
1215- @ Test
1216- public void testIngestSizeLimits () throws InterruptedException , IOException {
1217- Response createUser = UtilIT .createRandomUser ();
1218- createUser .then ().assertThat ().statusCode (OK .getStatusCode ());
1219- String username = UtilIT .getUsernameFromResponse (createUser );
1220- String apiToken = UtilIT .getApiTokenFromResponse (createUser );
1221- Response makeSuperUser = UtilIT .setSuperuserStatus (username , true );
1222- makeSuperUser .then ().assertThat ().statusCode (OK .getStatusCode ());
1223-
1224- Response createDataverseResponse = UtilIT .createRandomDataverse (apiToken );
1225- createDataverseResponse .prettyPrint ();
1226- String dataverseAlias = UtilIT .getAliasFromResponse (createDataverseResponse );
1227- Response createDatasetResponse = UtilIT .createRandomDatasetViaNativeApi (dataverseAlias , apiToken );
1228- createDatasetResponse .prettyPrint ();
1229- Integer datasetId = JsonPath .from (createDatasetResponse .body ().asString ()).getInt ("data.id" );
1230-
1231- String tinyCsvOnly = """
1232- {
1233- "csv": "50"
1234- }
1235- """ ;
1236-
1237- Response setLimit = UtilIT .setSetting (SettingsServiceBean .Key .TabularIngestSizeLimit , tinyCsvOnly );
1238- setLimit .then ().assertThat ().statusCode (OK .getStatusCode ());
1239-
1240- Path pathToDataFile = Paths .get (java .nio .file .Files .createTempDirectory (null ) + File .separator + "data.csv" );
1241- String contentOfCsv = ""
1242- + "name,pounds,species,treats\n "
1243- + "Midnight,15,dog,milkbones\n "
1244- + "Tiger,17,cat,cat grass\n "
1245- + "Panther,21,cat,cat nip\n " ;
1246- java .nio .file .Files .write (pathToDataFile , contentOfCsv .getBytes ());
1247-
1248- Response uploadFile = UtilIT .uploadFileViaNative (datasetId .toString (), pathToDataFile .toString (), apiToken );
1249- uploadFile .prettyPrint ();
1250- uploadFile .then ().assertThat ()
1251- .statusCode (OK .getStatusCode ())
1252- .body ("data.files[0].label" , equalTo ("data.csv" ));
1253-
1254- String fileId1 = JsonPath .from (uploadFile .body ().asString ()).getString ("data.files[0].dataFile.id" );
1255-
1256- Response getTabularFails = UtilIT .getFileDataTables (fileId1 , apiToken );
1257- getTabularFails .prettyPrint ();
1258- getTabularFails .then ().assertThat ()
1259- .statusCode (BAD_REQUEST .getStatusCode ())
1260- .body ("message" , equalTo (BundleUtil .getStringFromBundle ("files.api.only.tabular.supported" )));
1261-
1262- String largerCsv = """
1263- {
1264- "csv": "123456"
1265- }
1266- """ ;
1267-
1268- setLimit = UtilIT .setSetting (SettingsServiceBean .Key .TabularIngestSizeLimit , largerCsv );
1269- setLimit .then ().assertThat ().statusCode (OK .getStatusCode ());
1270-
1271- uploadFile = UtilIT .uploadFileViaNative (datasetId .toString (), pathToDataFile .toString (), apiToken );
1272- uploadFile .prettyPrint ();
1273- uploadFile .then ().assertThat ()
1274- .statusCode (OK .getStatusCode ())
1275- .body ("data.files[0].label" , equalTo ("data-1.csv" ));
1276-
1277- assertTrue (UtilIT .sleepForLock (datasetId .longValue (), "Ingest" , apiToken , UtilIT .MAXIMUM_INGEST_LOCK_DURATION ), "Failed test if Ingest Lock exceeds max duration " + pathToDataFile );
1278-
1279- String fileId2 = JsonPath .from (uploadFile .body ().asString ()).getString ("data.files[0].dataFile.id" );
1280-
1281- Response getTabularWorks = UtilIT .getFileDataTables (fileId2 , apiToken );
1282- getTabularWorks .prettyPrint ();
1283- getTabularWorks .then ().assertThat ()
1284- .statusCode (OK .getStatusCode ())
1285- .body ("data[0].varQuantity" , equalTo (4 ));
1286-
1287- String tinyDefaultSize = """
1288- {
1289- "default": "50"
1290- }
1291- """ ;
1292-
1293- setLimit = UtilIT .setSetting (SettingsServiceBean .Key .TabularIngestSizeLimit , tinyDefaultSize );
1294- setLimit .then ().assertThat ().statusCode (OK .getStatusCode ());
1295-
1296- uploadFile = UtilIT .uploadFileViaNative (datasetId .toString (), pathToDataFile .toString (), apiToken );
1297- uploadFile .prettyPrint ();
1298- uploadFile .then ().assertThat ()
1299- .statusCode (OK .getStatusCode ())
1300- .body ("data.files[0].label" , equalTo ("data-2.csv" ));
1301-
1302- String fileId3 = JsonPath .from (uploadFile .body ().asString ()).getString ("data.files[0].dataFile.id" );
1303-
1304- getTabularFails = UtilIT .getFileDataTables (fileId3 , apiToken );
1305- getTabularFails .prettyPrint ();
1306- getTabularFails .then ().assertThat ()
1307- .statusCode (BAD_REQUEST .getStatusCode ())
1308- .body ("message" , equalTo (BundleUtil .getStringFromBundle ("files.api.only.tabular.supported" )));
1309-
1310- // The behavior of `"default": "-2"` is not documented in the guides
1311- // but it acts like `"default": "0"` which disables ingest.
1312- String unexpectedNegativeDefault = """
1313- {
1314- "default": "-2"
1315- }
1316- """ ;
1317-
1318- setLimit = UtilIT .setSetting (SettingsServiceBean .Key .TabularIngestSizeLimit , unexpectedNegativeDefault );
1319- setLimit .then ().assertThat ().statusCode (OK .getStatusCode ());
1320-
1321- uploadFile = UtilIT .uploadFileViaNative (datasetId .toString (), pathToDataFile .toString (), apiToken );
1322- uploadFile .prettyPrint ();
1323- uploadFile .then ().assertThat ()
1324- .statusCode (OK .getStatusCode ())
1325- .body ("data.files[0].label" , equalTo ("data-3.csv" ));
1326-
1327- String fileId4 = JsonPath .from (uploadFile .body ().asString ()).getString ("data.files[0].dataFile.id" );
1328-
1329- getTabularFails = UtilIT .getFileDataTables (fileId4 , apiToken );
1330- getTabularFails .prettyPrint ();
1331- getTabularFails .then ().assertThat ()
1332- .statusCode (BAD_REQUEST .getStatusCode ())
1333- .body ("message" , equalTo (BundleUtil .getStringFromBundle ("files.api.only.tabular.supported" )));
1334-
1335- // As the guides say, you MUST provide a string, not a JSON number.
1336- // That is, `"123"` in quotes rather than `123` with no quotes.
1337- // If you provide a number (no quotes) rather than a string,
1338- // all ingest will be disabled and you'll see an error in server.log
1339- // about how the system is misconfigured.
1340- String invalidNonString = """
1341- {
1342- "default": 987654321
1343- }
1344- """ ;
1345-
1346- setLimit = UtilIT .setSetting (SettingsServiceBean .Key .TabularIngestSizeLimit , invalidNonString );
1347- setLimit .then ().assertThat ().statusCode (OK .getStatusCode ());
1348-
1349- uploadFile = UtilIT .uploadFileViaNative (datasetId .toString (), pathToDataFile .toString (), apiToken );
1350- uploadFile .prettyPrint ();
1351- uploadFile .then ().assertThat ()
1352- .statusCode (OK .getStatusCode ())
1353- .body ("data.files[0].label" , equalTo ("data-4.csv" ));
1354-
1355- String fileId5 = JsonPath .from (uploadFile .body ().asString ()).getString ("data.files[0].dataFile.id" );
1356-
1357- getTabularFails = UtilIT .getFileDataTables (fileId5 , apiToken );
1358- getTabularFails .prettyPrint ();
1359- getTabularFails .then ().assertThat ()
1360- .statusCode (BAD_REQUEST .getStatusCode ())
1361- .body ("message" , equalTo (BundleUtil .getStringFromBundle ("files.api.only.tabular.supported" )));
1362-
1363- String defaultDisabledAndLargeCsvLimit = """
1364- {
1365- "default": "0",
1366- "csv": "123456"
1367- }
1368- """ ;
1369-
1370- setLimit = UtilIT .setSetting (SettingsServiceBean .Key .TabularIngestSizeLimit , defaultDisabledAndLargeCsvLimit );
1371- setLimit .then ().assertThat ().statusCode (OK .getStatusCode ());
1372-
1373- uploadFile = UtilIT .uploadFileViaNative (datasetId .toString (), pathToDataFile .toString (), apiToken );
1374- uploadFile .prettyPrint ();
1375- uploadFile .then ().assertThat ()
1376- .statusCode (OK .getStatusCode ())
1377- .body ("data.files[0].label" , equalTo ("data-5.csv" ));
1378-
1379- String fileId6 = JsonPath .from (uploadFile .body ().asString ()).getString ("data.files[0].dataFile.id" );
1380-
1381- getTabularWorks = UtilIT .getFileDataTables (fileId2 , apiToken );
1382- getTabularWorks .prettyPrint ();
1383- getTabularWorks .then ().assertThat ()
1219+
1220+ @ Nested
1221+ class IngestSizeLimits {
1222+
1223+ static String apiToken ;
1224+ static int datasetId ;
1225+ @ TempDir
1226+ static Path tempDir ;
1227+ static String csvFileName = "data.csv" ;
1228+ static Path csvFile ;
1229+ static final String csvData =
1230+ """
1231+ name,pounds,species,treats
1232+ Midnight,15,dog,milkbones
1233+ Tiger,17,cat,cat grass
1234+ Panther,21,cat,cat nip
1235+ """ ;
1236+
1237+ @ BeforeAll
1238+ static void setup () throws IOException {
1239+ // Create random user, collection and dataset to work with
1240+ Response createUser = UtilIT .createRandomUser ();
1241+ createUser .then ().assertThat ().statusCode (OK .getStatusCode ());
1242+ String username = UtilIT .getUsernameFromResponse (createUser );
1243+ apiToken = UtilIT .getApiTokenFromResponse (createUser );
1244+ Response makeSuperUser = UtilIT .setSuperuserStatus (username , true );
1245+ makeSuperUser .then ().assertThat ().statusCode (OK .getStatusCode ());
1246+
1247+ Response createDataverseResponse = UtilIT .createRandomDataverse (apiToken );
1248+ createDataverseResponse .prettyPrint ();
1249+ String dataverseAlias = UtilIT .getAliasFromResponse (createDataverseResponse );
1250+ Response createDatasetResponse = UtilIT .createRandomDatasetViaNativeApi (dataverseAlias , apiToken );
1251+ createDatasetResponse .prettyPrint ();
1252+ datasetId = JsonPath .from (createDatasetResponse .body ().asString ()).getInt ("data.id" );
1253+
1254+ // Create CSV datafile to work with
1255+ csvFile = tempDir .resolve (csvFileName );
1256+ java .nio .file .Files .writeString (csvFile , csvData , StandardCharsets .UTF_8 );
1257+ }
1258+
1259+ @ AfterAll
1260+ static void teardown () {
1261+ // Remove the setting for test isolation purposes
1262+ Response removeLimit = UtilIT .deleteSetting (Key .TabularIngestSizeLimit );
1263+ removeLimit .then ().assertThat ().statusCode (OK .getStatusCode ());
1264+ }
1265+
1266+ static List <Arguments > configurations () {
1267+ return List .of (
1268+ // Too small for 134 chars
1269+ Arguments .of ("{\" csv\" : 50}" , BAD_REQUEST , "message" , equalTo (BundleUtil .getStringFromBundle ("files.api.only.tabular.supported" ))),
1270+ Arguments .of ("{\" default\" : 50.0}" , BAD_REQUEST , "message" , equalTo (BundleUtil .getStringFromBundle ("files.api.only.tabular.supported" ))),
1271+
1272+ // The behavior of `"default": "-2"` is not documented in the guides
1273+ // but it acts like `"default": "0"` which disables ingest.
1274+ Arguments .of ("{\" default\" : -2}" , BAD_REQUEST , "message" , equalTo (BundleUtil .getStringFromBundle ("files.api.only.tabular.supported" ))),
1275+
1276+ // Large enough :-)
1277+ Arguments .of ("{\" csv\" : 123456}" , OK , "data[0].varQuantity" , equalTo (4 )),
1278+ // Default is disabled, but exception for CSV
1279+ Arguments .of ("{\" default\" : 0,\" csv\" : 123456}" , OK , "data[0].varQuantity" , equalTo (4 ))
1280+ );
1281+ }
1282+
1283+ @ ParameterizedTest
1284+ @ MethodSource ("configurations" )
1285+ void testIngestSizeLimits (String ingestSizeLimitConfig , Status expectedStatus , String jsonPath , Matcher matcher ) throws IOException {
1286+ // given
1287+ Response setLimit = UtilIT .setSetting (Key .TabularIngestSizeLimit , ingestSizeLimitConfig );
1288+ setLimit .then ().assertThat ().statusCode (OK .getStatusCode ());
1289+
1290+ // when
1291+ Response uploadResponse = UtilIT .uploadFileViaNative (Integer .toString (datasetId ), csvFile .toString (), apiToken );
1292+ //uploadResponse.prettyPrint();
1293+ uploadResponse .then ().assertThat ()
13841294 .statusCode (OK .getStatusCode ())
1385- .body ("data[0].varQuantity" , equalTo (4 ));
1386-
1387- Response removeLimit = UtilIT .deleteSetting (SettingsServiceBean .Key .TabularIngestSizeLimit );
1388- removeLimit .then ().assertThat ().statusCode (OK .getStatusCode ());
1295+ .body ("data.files[0].label" , equalTo (csvFileName ));
1296+ String fileId = JsonPath .from (uploadResponse .body ().asString ()).getString ("data.files[0].dataFile.id" );
1297+
1298+ // Wait for ingest to complete
1299+ assertTrue (UtilIT .sleepForLock (datasetId , "Ingest" , apiToken , UtilIT .MAXIMUM_INGEST_LOCK_DURATION ),
1300+ "Failed test if Ingest Lock exceeds max duration" );
1301+
1302+ // then
1303+ Response getTabularFails = UtilIT .getFileDataTables (fileId , apiToken );
1304+ //getTabularFails.prettyPrint();
1305+ getTabularFails .then ().assertThat ()
1306+ .statusCode (expectedStatus .getStatusCode ())
1307+ .body (jsonPath , matcher );
1308+
1309+ // delete the file for the next test
1310+ UtilIT .deleteFile (Integer .valueOf (fileId ), apiToken );
1311+ }
13891312 }
13901313
1314+
13911315 @ Test
13921316 public void testUningestFileViaApi () throws InterruptedException {
13931317 Response createUser = UtilIT .createRandomUser ();
0 commit comments