diff --git a/tests/smoke/kotlin/README.md b/tests/smoke/kotlin/README.md index fee8e2c5a..0d88400db 100644 --- a/tests/smoke/kotlin/README.md +++ b/tests/smoke/kotlin/README.md @@ -1,80 +1,113 @@ -# Upload API Smoke Tests with TestNG - -This is a Kotlin/Gradle project that uses the TestNG framework to automate smoke testing for the Upload API. These tests -upload actual files and verify the functionality for metadata verification, file copy, and Processing Status API -integration. They are intended to be run after a release to the tst, stg, or prd environments. They can also be run -locally on a machine within the CDC network. +# Upload API End to End Tests with TestNG +This is a Kotlin/Gradle project that uses the TestNG framework to automate end to end testing for the Upload API. These tests upload actual files and verify the functionality for metadata verification, file copy, Processing Status API integration and healthcheck testing. They are intended to be run after a release to the `DEV`, `TEST` or `STAGE` environments or for frequent checking of functionality against any environment, including local environments. ## Local Setup The following tools need to be installed on your machine: -- Java JDK 17 -- Gradle +- [Java JDK 17](https://www.oracle.com/java/technologies/javase/jdk17-archive-downloads.html) +- [Gradle](https://gradle.org/install/) + +> [!TIP] +> For Windows users, the `gradlew` batch file in the repo can be used to execute gradle commands as long as Gradle is installed. ### Install Gradle Dependencies -Next, run `gradle build` to install dependencies for this project. This also installs the TestNG dependency. **Note -that you may need to turn off Zscalar in order for this operation to be successful.** +Next, run `gradle build` to install dependencies for this project. This also installs the TestNG dependency and other related dependencies for the project. + +> [!NOTE] +> You may need to turn off Zscalar in order for this operation to be successful. ### Environment Setup -Next, set required environment variables. This can be done by setting local gradle properties in a `local.properties` -file at the root level, or passing them in on the command line as Java system properties. To see a list of required -variables, look at the `src/test/kotlin/util/EnvConfig.kt` file. -These environment variables are how you target different DEX environments. For example, for the dev environment, all -environment variables need to point to URLs, endpoints, and services uses by the Upload API dev environment. +Copy `local.properites-example` to `local.properties` for the basic required environment variables at the root level of the project. -The EnvConfig class reads configuration values from a local.properties file. This setup allows us to manage environment-specific settings, like URLs and credentials. +The following are the currently configured environment variables that can be set for the tests to run. -Storage account keys can be found in our key vaults (ocio--upload-vault). -Example of dev key vault: `ocio-dev-upload-vault - Microsoft Azure`. Need to have `su` account for Microsoft Azure portal as a pre-requisite. +| Environment Variable | Required? | Description | +| ---------------------| --------- | -------------------------------------------------| +| environment | Yes | Environment to target (`LOCAL`, `DEV`, `TEST`, `STAGE`) +| upload.url | Yes | URL of the upload API +| ps.api.url | Yes | URL for Processing Status API +| sams.username | No | SAMS username for authentication (if needed) +| sams.password | No | SAMS password for authentication (if needed) -![img.png](img.png) +The EnvConfig class (`src/test/kotlin/util/EnvConfig.kt`) reads configuration values from a local.properties file. This setup allows us to manage environment-specific settings, like URLs and credentials. ### Running tests -This project contains a set of test suites that define the tests to be run. These suites are grouped by environment and -are broken up by use case. -They are run by executing the `gradle test` comment with a few gradle properties that are passed in as command line -arguments: +This project contains a set of test suites that define the tests to be run for different functional areas. Tests are located in `/src/test/kotlin`. The test files are: +| Test File | Purpose | +|----------------|----------------| +| `FileCopy.kt ` | Testing that the upload api can accept files for different manifest configurations and can upload and transfer files as expected. +| `Health.kt` | Test the healthcheck endpoint +| `Info.kt ` | Test the /info endpoint +| `ProcStat.kt` | Testing processing status reports after an upload + +They are run by executing the `gradle test` with the option of using some command line parameters. + +#### Optional Parameters -- `manifestFilter` - This is a required argument that allows you to select a subset of use cases you want to run the - tests against. It is a comma-separated list of values for each key that you want to run. Available manifests are added - to a file which is under resources folder in json format. By default, the tests will run for all use cases. +- `manifestFilter` - This argument allows you to select a subset of use cases you want to run the tests against. It is a comma-separated list of values for each key that you want to run. Available manifests are added to a file which is under resources folder in json format. By default, the tests will run for all use cases. #### Examples: +The following are some examples of test run commands. -- Run all use cases: +> [!TIP] +> The `--tests` parameter lets you select only a specific test file to run or a specific test within a file. -`gradle test` +> [!TIP] +> The `--rerun` command may be needed in order to force tests to rerun, otherwise tests may be skipped if there are no changes. -## Data Providers for TestNG +##### Run All Tests + +``` +gradle test +``` + +##### Run tests from a specific file -This project includes a `DataProvider` utility class that supplies test data to TestNG tests. The class uses the Jackson -library to read and filter JSON manifests based on criteria specified through system properties. +``` +gradle test --tests "FileCopy" +``` -### How It Works +##### Run tests from a specific file and a specific test -1. Loading Manifests: +``` +gradle test --tests "FileCopy.shouldUploadFile" +``` -- The `DataProvider` class reads JSON manifest files specified in the data provider methods. +##### Run tests filtered by manifest -2. Filtering Manifests: +> [!TIP] +> This filter is a semicolon-separated string of key-value pairs. +> Each key can have multiple comma-separated values. +``` +gradle test -PmanifestFilter='data_stream_id=ehdi' +``` +``` +gradle test -PmanifestFilter='meta_destination_id=ndlp&meta_ext_source=IZGW' +``` +``` +gradle test -PmanifestFilter='jurisdiction=AKA,CA;data_stream_route=csv,other&sender_id=CA-ABCs,IZGW' +``` + +## Data Providers for TestNG -- A system property `manifestFilter` can be set to define filtering criteria. -- This filter is a semicolon-separated string of key-value pairs. -- Each key can have multiple comma-separated values. +This project includes a `DataProvider` utility class that supplies test data to TestNG tests. The test data being used in tests are essentially test cases that define how a single test can be repeated and validated for different cases defined within the json being used as a source for the DataProvider. -### Example Usage +### Data Provider Definitions -To filter specific key-value pairs in the JSON manifests, use the `manifestFilter` system property. -Here are the example commands to run the tests with manifest filters: +The `dataProvider` decorator before a test defines what data provider should be used to pass in data into the test. These are the currently defined Data Providers in `/src/test/kotlin/util/DataProvider.kt` -`gradle test -PmanifestFilter='meta_destination_id=ndlp&meta_ext_source=IZGW'` -`gradle test -PmanifestFilter='jurisdiction=AKA,CA;data_stream_route=csv,other&sender_id=CA-ABCs,IZGW'` +| Data Provider Name | Associated json file | Description | +|-----------------------------------------|-------------------------------------------|-----------------------------| +| `versionProvider` | N/A | Returns an array of `["v1", "v2"]` for versions +| `validManifestAllProvider` | `valid_manifests_v2.json` | All v2 manifests and path configs | +| `validManifestV1Provider` | `valid_manifests_v1.json` | All v1 manifests and configs | +| `invalidManifestRequiredFieldsProvider` | `invalid_manifests_required_fields.json` | Manifests with invalid values | +| `invalidManifestInvalidValueProvider` | `invalid_manifests_invalid_value.json` | Manifests with invalid fields | -We can run tests for specified manifest in a single command line argument. ### Future Improvements diff --git a/tests/smoke/kotlin/local.properties-example b/tests/smoke/kotlin/local.properties-example index 6a71fa71a..3e8396f4a 100644 --- a/tests/smoke/kotlin/local.properties-example +++ b/tests/smoke/kotlin/local.properties-example @@ -1,35 +1,29 @@ +#local +environment=LOCAL +upload.url=http://localhost:8080 +ps.api.url=http://localhost:8080 +sams.username= +sams.password= + #dev +environment=DEV upload.url=https://apidev.cdc.gov ps.api.url=https://dex-dev-svc.cdc.gov sams.username= sams.password= -dex.storage.connection.string= -edav.storage.account.name= -routing.storage.connection.string= -azure.client.id= -azure.client.secret= -azure.tenant.id= + # tst +environment=TEST upload.url=https://apitst.cdc.gov ps.api.url=https://dex-tst-svc.cdc.gov sams.username= sams.password= -dex.storage.connection.string= -edav.storage.account.name= -routing.storage.connection.string= -azure.client.id= -azure.client.secret= -azure.tenant.id= + # stg +environment=STAGE upload.url=https://apistg.cdc.gov ps.api.url=https://dex-stg-svc.cdc.gov sams.username= sams.password= -dex.storage.connection.string= -edav.storage.account.name= -routing.storage.connection.string= -azure.client.id= -azure.client.secret= -azure.tenant.id= \ No newline at end of file diff --git a/tests/smoke/kotlin/src/test/kotlin/FileCopy.kt b/tests/smoke/kotlin/src/test/kotlin/FileCopy.kt index 0c1093df5..79a578e9e 100644 --- a/tests/smoke/kotlin/src/test/kotlin/FileCopy.kt +++ b/tests/smoke/kotlin/src/test/kotlin/FileCopy.kt @@ -1,5 +1,3 @@ -import com.azure.identity.ClientSecretCredentialBuilder -import com.azure.storage.blob.BlobClient import dex.DexUploadClient import org.testng.Assert import org.testng.ITestContext @@ -9,35 +7,24 @@ import tus.UploadClient import util.* import util.ConfigLoader.Companion.loadUploadConfig import util.DataProvider +import java.net.URLDecoder +import java.nio.charset.StandardCharsets +import java.time.ZonedDateTime +import java.util.TimeZone import kotlin.collections.HashMap - @Listeners(UploadIdTestListener::class) @Test() class FileCopy { private val testFile = TestFile.getResourceFile("10KB-test-file") - private val authClient = DexUploadClient(EnvConfig.UPLOAD_URL) - private val dexBlobClient = Azure.getBlobServiceClient(EnvConfig.DEX_STORAGE_CONNECTION_STRING) - private val edavBlobClient = Azure.getBlobServiceClient( - EnvConfig.EDAV_STORAGE_ACCOUNT_NAME, - ClientSecretCredentialBuilder() - .clientId(EnvConfig.AZURE_CLIENT_ID) - .clientSecret(EnvConfig.AZURE_CLIENT_SECRET) - .tenantId(EnvConfig.AZURE_TENANT_ID) - .build() - ) - private val routingBlobClient = Azure.getBlobServiceClient(EnvConfig.ROUTING_STORAGE_CONNECTION_STRING) - private val bulkUploadsContainerClient = dexBlobClient.getBlobContainerClient(Constants.BULK_UPLOAD_CONTAINER_NAME) - private val edavContainerClient = edavBlobClient.getBlobContainerClient(Constants.EDAV_UPLOAD_CONTAINER_NAME) - private val routingContainerClient = - routingBlobClient.getBlobContainerClient(Constants.ROUTING_UPLOAD_CONTAINER_NAME) + private val dexUploadClient = DexUploadClient(EnvConfig.UPLOAD_URL) private lateinit var authToken: String private lateinit var testContext: ITestContext private lateinit var uploadClient: UploadClient @BeforeTest(groups = [Constants.Groups.FILE_COPY]) fun beforeFileCopy() { - authToken = authClient.getToken(EnvConfig.SAMS_USERNAME, EnvConfig.SAMS_PASSWORD) + authToken = dexUploadClient.getToken(EnvConfig.SAMS_USERNAME, EnvConfig.SAMS_PASSWORD) } @BeforeMethod @@ -51,41 +38,43 @@ class FileCopy { dataProvider = "validManifestAllProvider", dataProviderClass = DataProvider::class ) - fun shouldUploadFile(manifest: HashMap) { - val uid = uploadClient.uploadFile(testFile, manifest) + fun shouldUploadFile(case: TestCase) { + val uid = uploadClient.uploadFile(testFile, case.manifest) ?: throw TestNGException("Error uploading file ${testFile.name}") testContext.setAttribute("uploadId", uid) - Thread.sleep(2000) - - // First, check bulk upload and .info file. - val uploadBlob = bulkUploadsContainerClient.getBlobClient("${Constants.TUS_PREFIX_DIRECTORY_NAME}/$uid") - val uploadInfoBlob = - bulkUploadsContainerClient.getBlobClient("${Constants.TUS_PREFIX_DIRECTORY_NAME}/$uid.info") - - Assert.assertTrue(uploadBlob.exists()) - Assert.assertTrue(uploadInfoBlob.exists()) - Assert.assertEquals(uploadBlob.properties.blobSize, testFile.length()) - - // Next, check that the file arrived in destination storage. - val config = loadUploadConfig(dexBlobClient, manifest) - val filenameSuffix = Filename.getFilenameSuffix(config.copyConfig, uid) - val expectedFilename = "${ - Metadata.getFilePrefix(config.copyConfig, manifest) - }${Metadata.getFilename(manifest)}${filenameSuffix}${testFile.extension}" - var expectedBlobClient: BlobClient? - - if (config.copyConfig.targets.contains("edav")) { - expectedBlobClient = edavContainerClient.getBlobClient(expectedFilename) - - Assert.assertNotNull(expectedBlobClient) - Assert.assertEquals(expectedBlobClient!!.properties.blobSize, testFile.length()) - } - - if (config.copyConfig.targets.contains("routing")) { - expectedBlobClient = routingContainerClient.getBlobClient(expectedFilename) - - Assert.assertNotNull(expectedBlobClient) - Assert.assertEquals(expectedBlobClient!!.properties.blobSize, testFile.length()) + Thread.sleep(5000) + val uploadInfo = dexUploadClient.getFileInfo(uid, authToken) + + // Check File Info + val expectedBytes: Long = 10240 + Assert.assertEquals(uploadInfo.fileInfo.sizeBytes, expectedBytes) + + // Check Upload Status + Assert.assertEquals(uploadInfo.uploadStatus.status, "Complete", "File upload status is not 'Complete'") + + // Check Deliveries + Assert.assertEquals(uploadInfo.deliveries?.size, case.deliveryTargets?.size, "Expected ${case.deliveryTargets?.size ?: 0 } deliveries") + + val expectedDeliveryNames = case.deliveryTargets?.map{ it.name }?.sorted() + val actualDeliveryNames = uploadInfo.deliveries?.map{ it.name }?.sorted() + Assert.assertEquals(actualDeliveryNames, expectedDeliveryNames, "Actual delivery targets do not match expected targets") + + val currentDateTime = ZonedDateTime.now(TimeZone.getTimeZone("GMT").toZoneId()) + uploadInfo.deliveries?.forEach { delivery -> + Assert.assertEquals(delivery.status, "SUCCESS") // remove the assertion above? + val actualLocation = URLDecoder.decode(delivery.location, StandardCharsets.UTF_8.toString()) + val pattern = case.deliveryTargets?.find{ it.name == delivery.name}?.pathTemplate?.get(EnvConfig.ENVIRONMENT) + val expectedLocation = pattern + ?.replace("{dataStream}", case.manifest["data_stream_id"].toString()) + ?.replace("{route}", case.manifest["data_stream_route"].toString()) + ?.replace("{year}", currentDateTime.year.toString() ) + ?.replace("{month}", String.format("%02d", currentDateTime.monthValue) ) + ?.replace("{day}", String.format("%02d", currentDateTime.dayOfMonth) ) + ?.replace("{hour}", String.format("%02d", currentDateTime.hour) ) + ?.replace("{filename}", case.manifest["received_filename"].toString()) + ?.replace("{uploadId}",uid) + Assert.assertTrue(actualLocation.endsWith(expectedLocation.toString()), "Actual location ($actualLocation) does not end with the expected path: $expectedLocation") + Assert.assertEquals(delivery.issues, null) } } @@ -94,37 +83,18 @@ class FileCopy { dataProvider = "validManifestV1Provider", dataProviderClass = DataProvider::class ) - fun shouldTranslateMetadataGivenV1SenderManifest(manifest: HashMap) { - val useCase = Metadata.getUseCaseFromManifest(manifest) - val dexContainerClient = dexBlobClient.getBlobContainerClient(useCase) - val v1Config = loadUploadConfig(dexBlobClient, manifest) - val v2ConfigFilename = v1Config.compatConfigFilename ?: "$useCase.json" - val v2Config = loadUploadConfig(dexBlobClient, v2ConfigFilename, "v2") - val metadataMapping = v2Config.metadataConfig.fields.filter { it.compatFieldName != null } - .associate { it.compatFieldName to it.fieldName } - - val uid = uploadClient.uploadFile(testFile, manifest) + fun shouldTranslateMetadataGivenV1SenderManifest(case: TestCase) { + val uid = uploadClient.uploadFile(testFile, case.manifest) ?: throw TestNGException("Error uploading file ${testFile.name}") testContext.setAttribute("uploadId", uid) - Thread.sleep(1000) - - val filenameSuffix = Filename.getFilenameSuffix(v1Config.copyConfig, uid) - val expectedFilename = - "${Metadata.getFilePrefix(v1Config.copyConfig)}${Metadata.getFilename(manifest)}$filenameSuffix${testFile.extension}" - val expectedBlobClient = dexContainerClient.getBlobClient(expectedFilename) - val blobMetadata = expectedBlobClient.properties.metadata - - metadataMapping.forEach { (v1Key, v2Key) -> - Assert.assertTrue( - blobMetadata.containsKey(v2Key), - "Mismatch: Blob metadata does not contain expected V2 key: $v2Key which should map from V1 key: $v1Key" - ) - } - - metadataMapping.forEach { (v1Key, v2Key) -> - val v1Val = manifest[v1Key] ?: "" - val v2Val = blobMetadata[v2Key] ?: "" - Assert.assertEquals(v1Val, v2Val, "Expected V1 value: $v1Val does not match with actual V2 value: $v2Val") - } + Thread.sleep(3000) + + val uploadInfo = dexUploadClient.getFileInfo(uid, authToken) + //Assert.assertTrue(uploadInfo.deliveries?.all { it.status == "SUCCESS" }?:false, "Not all deliveries are 'SUCCESS' - Deliveries: ${uploadInfo.deliveries}") + case.manifest.forEach{(manifestKey, manifestValue) -> + Assert.assertEquals(uploadInfo.manifest[manifestKey], manifestValue.toString(), "Actual manifest value does not equal the expected manifest value") + } + Assert.assertNotNull(uploadInfo.manifest["dex_ingest_datetime"]) + Assert.assertEquals(uploadInfo.manifest["upload_id"], uid, "Upload ID on the manifest is not the expected upload ID") } -} \ No newline at end of file +} diff --git a/tests/smoke/kotlin/src/test/kotlin/ProcStat.kt b/tests/smoke/kotlin/src/test/kotlin/ProcStat.kt index 8715af7e8..b34f9a15e 100644 --- a/tests/smoke/kotlin/src/test/kotlin/ProcStat.kt +++ b/tests/smoke/kotlin/src/test/kotlin/ProcStat.kt @@ -4,19 +4,19 @@ import dex.DexUploadClient import org.testng.ITestContext import org.testng.TestNGException import org.testng.annotations.* -import util.* -import util.DataProvider import org.testng.Assert.* import org.slf4j.LoggerFactory import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import model.DataResponse +import util.* +import util.DataProvider @Listeners(UploadIdTestListener::class) @Test() class ProcStat { private val testFile = TestFile.getResourceFile("10KB-test-file") private val authClient = DexUploadClient(EnvConfig.UPLOAD_URL) - private val dexBlobClient = Azure.getBlobServiceClient(EnvConfig.DEX_STORAGE_CONNECTION_STRING) + //private val dexBlobClient = Azure.getBlobServiceClient(EnvConfig.DEX_STORAGE_CONNECTION_STRING) private val procStatReqSpec = given().relaxedHTTPSValidation() .apply { baseUri(EnvConfig.PROC_STAT_URL) @@ -42,9 +42,9 @@ class ProcStat { dataProvider = "validManifestAllProvider", dataProviderClass = DataProvider::class ) - fun shouldHaveReportsForSuccessfulFileUpload(manifest: HashMap, testContext: ITestContext) { - val config = ConfigLoader.loadUploadConfig(dexBlobClient, manifest) - val uid = uploadClient.uploadFile(testFile, manifest) + fun shouldHaveReportsForSuccessfulFileUpload(case: TestCase, testContext: ITestContext) { + //val config = ConfigLoader.loadUploadConfig(dexBlobClient, case.manifest) + val uid = uploadClient.uploadFile(testFile, case.manifest) ?: throw TestNGException("Error uploading file ${testFile.name}") testContext.setAttribute("uploadId", uid) log.debug("File uploaded successfully with UID: $uid") @@ -158,16 +158,11 @@ class ProcStat { assertNotNull(destinationName, "Destination name is missing for $schemaName") log.info("Blob File Copy - Source URL: $sourceUrl, Destination URL: $destinationUrl") - val expectedDestinations = config.copyConfig.targets + val expectedDestinations = case.deliveryTargets?.map{ it.name }?.sorted() - val actualDestination = expectedDestinations.any { expectedDest -> - destinationName?.trim()?.equals(expectedDest.trim(), ignoreCase = true) ?: false + if(expectedDestinations != null){ + assertTrue(expectedDestinations.contains(destinationName), "Actual destination $destinationName is not in expected target destinations ${expectedDestinations.toString()}") } - - assertTrue( - actualDestination, - "None of the expected destinations ('${expectedDestinations.joinToString(", ")}') were found in the response. Found: $destinationName" - ) } else -> { diff --git a/tests/smoke/kotlin/src/test/kotlin/model/FileDelivery.kt b/tests/smoke/kotlin/src/test/kotlin/model/FileDelivery.kt index 42e72b90a..ba6ebdecf 100644 --- a/tests/smoke/kotlin/src/test/kotlin/model/FileDelivery.kt +++ b/tests/smoke/kotlin/src/test/kotlin/model/FileDelivery.kt @@ -7,5 +7,5 @@ data class FileDelivery( val name: String, val location: String, @get:JsonProperty("delivered_at") val deliveredAt: String, - val issues: List? -) \ No newline at end of file + val issues: List>? +) diff --git a/tests/smoke/kotlin/src/test/kotlin/model/InfoResponse.kt b/tests/smoke/kotlin/src/test/kotlin/model/InfoResponse.kt index c397ffdd0..9bf717f0d 100644 --- a/tests/smoke/kotlin/src/test/kotlin/model/InfoResponse.kt +++ b/tests/smoke/kotlin/src/test/kotlin/model/InfoResponse.kt @@ -5,8 +5,8 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties @JsonIgnoreProperties(ignoreUnknown = true) data class InfoResponse( - val manifest: HashMap, + @get:JsonProperty("manifest") val manifest: HashMap, @get:JsonProperty("file_info") val fileInfo: FileInfo, @get:JsonProperty("upload_status") val uploadStatus: UploadStatus, @get:JsonProperty("deliveries") val deliveries: List? -) \ No newline at end of file +) diff --git a/tests/smoke/kotlin/src/test/kotlin/util/ConfigLoader.kt b/tests/smoke/kotlin/src/test/kotlin/util/ConfigLoader.kt index b9812ae3e..3738875ae 100644 --- a/tests/smoke/kotlin/src/test/kotlin/util/ConfigLoader.kt +++ b/tests/smoke/kotlin/src/test/kotlin/util/ConfigLoader.kt @@ -21,7 +21,7 @@ class ConfigLoader { } } - fun loadUploadConfig(dexBlobClient: BlobServiceClient, manifest: HashMap): UploadConfig { + fun loadUploadConfig(dexBlobClient: BlobServiceClient, manifest: Map): UploadConfig { val versionFolder = when (manifest["version"]) { "2.0" -> "v2" else -> "v1" diff --git a/tests/smoke/kotlin/src/test/kotlin/util/Constants.kt b/tests/smoke/kotlin/src/test/kotlin/util/Constants.kt index d9bb21a32..18ffaa2ac 100644 --- a/tests/smoke/kotlin/src/test/kotlin/util/Constants.kt +++ b/tests/smoke/kotlin/src/test/kotlin/util/Constants.kt @@ -1,5 +1,9 @@ package util +enum class Environment(val env: String) { + LOCAL("LOCAL"), DEV("DEV"), TEST("TEST"), STAGE("STAGE") +} + class Constants { companion object { const val TEST_DESTINATION = "dextesting" diff --git a/tests/smoke/kotlin/src/test/kotlin/util/DataProvider.kt b/tests/smoke/kotlin/src/test/kotlin/util/DataProvider.kt index 9f1aab3fd..1f479e63b 100644 --- a/tests/smoke/kotlin/src/test/kotlin/util/DataProvider.kt +++ b/tests/smoke/kotlin/src/test/kotlin/util/DataProvider.kt @@ -1,5 +1,6 @@ package util +import com.fasterxml.jackson.annotation.JsonProperty import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.module.kotlin.readValue import org.testng.annotations.DataProvider @@ -19,56 +20,56 @@ class DataProvider { @JvmStatic @DataProvider(name = "validManifestAllProvider") - fun validManifestAllProvider(): Array>> { - val validManifests = arrayOf("valid_manifests_v1.json", "valid_manifests_v2.json") - val manifests = loadManifests(validManifests) + fun validManifestAllProvider(): Array> { + val validManifests = arrayOf("valid_manifests_v2.json") + val cases = loadTestCases(validManifests) logger().info("Filtering all valid manifests") - return filterManifests(manifests) + return filterCases(cases) } @JvmStatic @DataProvider(name = "validManifestV1Provider") - fun validManifestV1Provider(): Array>> { + fun validManifestV1Provider(): Array> { val validManifests = arrayOf("valid_manifests_v1.json") - val manifests = loadManifests(validManifests) + val manifests = loadTestCases(validManifests) logger().info("Filtering V1 valid manifests") - return filterManifests(manifests) + return filterCases(manifests) } @JvmStatic @DataProvider(name = "invalidManifestRequiredFieldsProvider") - fun invalidManifestRequiredFieldsProvider(): Array>> { + fun invalidManifestRequiredFieldsProvider(): Array> { val invalidManifests = arrayOf("invalid_manifests_required_fields.json") - val manifests = loadManifests(invalidManifests) + val manifests = loadTestCases(invalidManifests) logger().info("Filtering invalid required field manifests") - return filterManifests(manifests) + return filterCases(manifests) } @JvmStatic @DataProvider(name = "invalidManifestInvalidValueProvider") - fun invalidManifestInvalidValueProvider(): Array>> { + fun invalidManifestInvalidValueProvider(): Array> { val invalidManifests = arrayOf("invalid_manifests_invalid_value.json") - val manifests = loadManifests(invalidManifests) + val manifests = loadTestCases(invalidManifests) logger().info("Filtering invalid value manifests") - return filterManifests(manifests) + return filterCases(manifests) } - private fun loadManifests(manifestFiles: Array): List> { - val manifests = arrayListOf>() + private fun loadTestCases(caseFiles: Array): List { + val cases = arrayListOf() - manifestFiles.forEach { manifestFile -> - val jsonBytes = TestFile.getResourceFile(manifestFile).readBytes() - val manifestJsons: List> = objectMapper.readValue(jsonBytes) - manifests.addAll(manifestJsons) + caseFiles.forEach { caseFile -> + val jsonBytes = TestFile.getResourceFile(caseFile).readBytes() + val caseJson: List = objectMapper.readValue(jsonBytes) + cases.addAll(caseJson) } - return manifests + return cases } - private fun filterManifests(manifests: List>): Array>> { + private fun filterCases(cases: List): Array> { val manifestFilter: String? = System.getProperty("manifestFilter") if (manifestFilter.isNullOrEmpty()) { - return toTypedMatrix(manifests) + return toTypedMatrix(cases) } val fields = manifestFilter.split("&") @@ -83,18 +84,28 @@ class DataProvider { manifestFilters[filterTokens[0]] = filterTokens[1].split(',') } - val filtered = manifests.filter { manifest -> + val filtered = cases.filter { case -> manifestFilters.all{ (field, allowedVals) -> - manifest[field]?.let { it in allowedVals } ?: false + case.manifest[field]?.let { it in allowedVals } ?: false } } - logger().info("Found ${filtered.size} manifests out of ${manifests.size}") + logger().info("Found ${filtered.size} manifests out of ${cases.size}") return toTypedMatrix(filtered) } - private fun toTypedMatrix(manifests: List>): Array>> { - return manifests.map { arrayOf(it) }.toTypedArray() + private inline fun toTypedMatrix(items: List): Array> { + return items.map { arrayOf(it) }.toTypedArray() } } -} \ No newline at end of file +} + +data class TestCase( + @JsonProperty("manifest")val manifest: Map, + @JsonProperty("delivery_targets") val deliveryTargets: List? +) + +data class Target( + @JsonProperty("name") val name: String, + @JsonProperty("path_template") val pathTemplate: Map +) diff --git a/tests/smoke/kotlin/src/test/kotlin/util/EnvConfig.kt b/tests/smoke/kotlin/src/test/kotlin/util/EnvConfig.kt index 06dbcb137..7c4d2ac08 100644 --- a/tests/smoke/kotlin/src/test/kotlin/util/EnvConfig.kt +++ b/tests/smoke/kotlin/src/test/kotlin/util/EnvConfig.kt @@ -9,15 +9,10 @@ class EnvConfig { private val properties = if (propFile.exists()) Properties().apply { load(File("local.properties").inputStream()) } else null + public final val ENVIRONMENT: Environment = Environment.valueOf(properties?.getProperty("environment") ?: System.getenv("ENVIRONMENT")) val UPLOAD_URL: String = properties?.getProperty("upload.url") ?: System.getenv("UPLOAD_URL") val PROC_STAT_URL: String = properties?.getProperty("ps.api.url") ?: System.getenv("PS_API_URL") val SAMS_USERNAME: String = properties?.getProperty("sams.username") ?: System.getenv("SAMS_USERNAME") val SAMS_PASSWORD: String = properties?.getProperty("sams.password") ?: System.getenv("SAMS_PASSWORD") - val DEX_STORAGE_CONNECTION_STRING: String = properties?.getProperty("dex.storage.connection.string") ?: System.getenv("DEX_STORAGE_CONNECTION_STRING") - val EDAV_STORAGE_ACCOUNT_NAME: String = properties?.getProperty("edav.storage.account.name") ?: System.getenv("EDAV_STORAGE_ACCOUNT_NAME") - val ROUTING_STORAGE_CONNECTION_STRING: String = properties?.getProperty("routing.storage.connection.string") ?: System.getenv("ROUTING_STORAGE_CONNECTION_STRING") - val AZURE_CLIENT_ID: String = properties?.getProperty("azure.client.id") ?: System.getenv("AZURE_CLIENT_ID") - val AZURE_CLIENT_SECRET: String = properties?.getProperty("azure.client.secret") ?: System.getenv("AZURE_CLIENT_SECRET") - val AZURE_TENANT_ID: String = properties?.getProperty("azure.tenant.id") ?: System.getenv("AZURE_TENANT_ID") } } \ No newline at end of file diff --git a/tests/smoke/kotlin/src/test/kotlin/util/Metadata.kt b/tests/smoke/kotlin/src/test/kotlin/util/Metadata.kt index 40b27b69b..596536975 100644 --- a/tests/smoke/kotlin/src/test/kotlin/util/Metadata.kt +++ b/tests/smoke/kotlin/src/test/kotlin/util/Metadata.kt @@ -43,7 +43,7 @@ class Metadata { return prefix } - fun getFilePrefix(copyConfig: CopyConfig, manifest: HashMap): String { + fun getFilePrefix(copyConfig: CopyConfig, manifest: Map): String { var prefix = "${getUseCaseFromManifest(manifest)}/" if (copyConfig.folderStructure == "date_YYYY_MM_DD") { @@ -79,7 +79,7 @@ class Metadata { } } - fun getFilename(manifest: HashMap): String { + fun getFilename(manifest: Map): String { val filenameKeys = arrayOf("received_filename", "meta_ext_filename", "filename", "original_filename") return manifest.entries.first { filenameKeys.contains(it.key) }.value diff --git a/tests/smoke/kotlin/src/test/resources/valid_manifests_v1.json b/tests/smoke/kotlin/src/test/resources/valid_manifests_v1.json index 541a517a2..51d3d8430 100644 --- a/tests/smoke/kotlin/src/test/resources/valid_manifests_v1.json +++ b/tests/smoke/kotlin/src/test/resources/valid_manifests_v1.json @@ -1,7 +1,9 @@ [ { - "meta_destination_id": "dextesting", - "meta_ext_event": "testevent1", - "filename": "dex-smoke-test" + "manifest": { + "meta_destination_id": "dextesting", + "meta_ext_event": "testevent1", + "filename": "dex-smoke-test" + } } -] \ No newline at end of file +] diff --git a/tests/smoke/kotlin/src/test/resources/valid_manifests_v2.json b/tests/smoke/kotlin/src/test/resources/valid_manifests_v2.json index 27aaa0b2e..44a365d13 100644 --- a/tests/smoke/kotlin/src/test/resources/valid_manifests_v2.json +++ b/tests/smoke/kotlin/src/test/resources/valid_manifests_v2.json @@ -1,111 +1,255 @@ [ { - "version": "2.0", - "data_stream_id": "covid-all-monthly-vaccination", - "data_stream_route": "csv", - "received_filename": "dex-smoke-test", - "jurisdiction": "XXA", - "data_producer_id": "XXA", - "sender_id": "IZGW", - "meta_ext_objectkey": "test-obj-key", - "meta_ext_submissionperiod": "smoke submission period", - "meta_ext_sourceversion": "V2022-12-31", - "meta_username": "test-username" + "manifest": { + "version": "2.0", + "data_stream_id": "dextesting", + "data_stream_route": "testevent1", + "received_filename": "dex-smoke-test", + "sender_id": "test sender", + "data_producer_id": "test-producer-id", + "jurisdiction": "test-jurisdiction" + }, + "delivery_targets": [ + { + "name": "edav", + "path_template": { + "LOCAL": "/upload/edav/{uploadId}", + "DEV": "/upload/{dataStream}-{route}/{year}/{month}/{day}/{filename}_{uploadId}", + "TEST": "/upload/{dataStream}-{route}/{year}/{month}/{day}/{filename}_{uploadId}", + "STAGE": "/upload/{dataStream}-{route}/{year}/{month}/{day}/{filename}_{uploadId}" + } + }, + { + "name": "ncird", + "path_template": { + "LOCAL": "/upload/ncird/{uploadId}", + "DEV": "/dex-dev/{dataStream}-{route}/{year}/{month}/{day}/{filename}_{uploadId}", + "TEST": "/dex-test/{dataStream}-{route}/{year}/{month}/{day}/{filename}_{uploadId}", + "STAGE": "/dex-stg/{dataStream}-{route}/{year}/{month}/{day}/{filename}_{uploadId}" + } + }, + { + "name": "ehdi", + "path_template": { + "LOCAL": "/upload/ehdi/{uploadId}", + "DEV": "/ddnid-ncbddd/DHDD/EHDI/DEX-DEV/{year}/{month}/{day}/{filename}_{uploadId}", + "TEST": "/ddnid-ncbddd/DHDD/EHDI/DEX-TST/{year}/{month}/{day}/{filename}_{uploadId}", + "STAGE": "/ddnid-ncbddd/DHDD/EHDI/DEX-STG/{year}/{month}/{day}/{filename}_{uploadId}" + } + }, + { + "name": "eicr", + "path_template": { + "LOCAL": "/upload/eicr/{uploadId}", + "DEV": "/ddphss-csels/DDTP/eICR Learning Pilot(eICRLRNG)/DEX-DEV/{year}/{month}/{day}/{filename}_{uploadId}", + "TEST": "/ddphss-csels/DDTP/eICR Learning Pilot(eICRLRNG)/DEX-TST/{year}/{month}/{day}/{filename}_{uploadId}", + "STAGE": "/ddphss-csels/DDTP/eICR Learning Pilot(eICRLRNG)/DEX-STG/{year}/{month}/{day}/{filename}_{uploadId}" + } + } + ] }, { - "version": "2.0", - "data_stream_id": "influenza-vaccination", - "data_stream_route": "csv", - "received_filename": "dex-smoke-test", - "jurisdiction": "XXA", - "data_producer_id": "XXA", - "sender_id": "IZGW", - "meta_ext_objectkey": "test-obj-key", - "meta_ext_submissionperiod": "smoke submission period", - "meta_ext_sourceversion": "V2022-12-31", - "meta_username": "test-username" + "manifest": { + "version": "2.0", + "data_stream_id": "covid-all-monthly-vaccination", + "data_stream_route": "csv", + "received_filename": "dex-smoke-test", + "jurisdiction": "XXA", + "data_producer_id": "XXA", + "sender_id": "IZGW", + "meta_ext_objectkey": "test-obj-key", + "meta_ext_submissionperiod": "smoke submission period", + "meta_ext_sourceversion": "V2022-12-31", + "meta_username": "test-username" + }, + "delivery_targets": [ + { + "name": "ncird", + "path_template": { + "LOCAL": "/upload/ncird/{uid}", + "DEV": "/dex-dev/{dataStream}-{route}/{year}/{month}/{day}/{filename}", + "TEST": "/dex-test/{dataStream}-{route}/{year}/{month}/{day}/{filename}", + "STAGE": "/dex/{dataStream}-{route}/{year}/{month}/{day}/{filename}" + } + } + ] }, { - "version": "2.0", - "data_stream_id": "routine-immunization", - "data_stream_route": "other", - "received_filename": "dex-smoke-test", - "jurisdiction": "XXA", - "data_producer_id": "XXA", - "sender_id": "IZGW", - "meta_ext_objectkey": "test-obj-key", - "meta_ext_submissionperiod": "smoke submission period", - "meta_ext_sourceversion": "V2022-12-31", - "meta_username": "test-username" + "manifest": { + "version": "2.0", + "data_stream_id": "influenza-vaccination", + "data_stream_route": "csv", + "received_filename": "dex-smoke-test", + "jurisdiction": "XXA", + "data_producer_id": "XXA", + "sender_id": "IZGW", + "meta_ext_objectkey": "test-obj-key", + "meta_ext_submissionperiod": "smoke submission period", + "meta_ext_sourceversion": "V2022-12-31", + "meta_username": "test-username" + }, + "delivery_targets": [ + { + "name": "ncird", + "path_template": { + "LOCAL": "/upload/ncird/{uid}", + "DEV": "/dex-dev/{dataStream}-{route}/{year}/{month}/{day}/{filename}", + "TEST": "/dex-test/{dataStream}-{route}/{year}/{month}/{day}/{filename}", + "STAGE": "/dex/{dataStream}-{route}/{year}/{month}/{day}/{filename}" + } + } + ] }, { - "version": "2.0", - "data_stream_id": "rsv-prevention", - "data_stream_route": "csv", - "received_filename": "dex-smoke-test", - "jurisdiction": "XXA", - "data_producer_id": "XXA", - "sender_id": "IZGW", - "meta_ext_objectkey": "test-obj-key", - "meta_ext_submissionperiod": "smoke submission period", - "meta_ext_sourceversion": "V2022-12-31", - "meta_username": "test-username" + "manifest": { + "version": "2.0", + "data_stream_id": "routine-immunization", + "data_stream_route": "other", + "received_filename": "dex-smoke-test", + "jurisdiction": "XXA", + "data_producer_id": "XXA", + "sender_id": "IZGW", + "meta_ext_objectkey": "test-obj-key", + "meta_ext_submissionperiod": "smoke submission period", + "meta_ext_sourceversion": "V2022-12-31", + "meta_username": "test-username" + }, + "delivery_targets": [ + { + "name": "ncird", + "path_template": { + "LOCAL": "/dex-test/routine-immunization-zip/{year}/{month}/{day}/{filename}", + "DEV": "/dex-dev/routine-immunization-zip/{year}/{month}/{day}/{filename}", + "TEST": "/dex-test/routine-immunization-zip/{year}/{month}/{day}/{filename}", + "STAGE": "/dex/routine-immunization-zip/{year}/{month}/{day}/{filename}" + } + } + ] }, { - "version": "2.0", - "data_stream_id": "eicr", - "data_stream_route": "fhir", - "sender_id": "APHL", - "data_producer_id": "test-producer-id", - "jurisdiction": "test-jurisdiction", - "received_filename": "dex-smoke-test", - "meta_ext_objectkey": "test-obj-key", - "meta_ext_file_timestamp": "test-file-timestamp" + "manifest": { + "version": "2.0", + "data_stream_id": "rsv-prevention", + "data_stream_route": "csv", + "received_filename": "dex-smoke-test", + "jurisdiction": "XXA", + "data_producer_id": "XXA", + "sender_id": "IZGW", + "meta_ext_objectkey": "test-obj-key", + "meta_ext_submissionperiod": "smoke submission period", + "meta_ext_sourceversion": "V2022-12-31", + "meta_username": "test-username" + }, + "delivery_targets": [ + { + "name": "ncird", + "path_template": { + "LOCAL": "/dex-test/{dataStream}-{route}/{year}/{month}/{day}/{filename}", + "DEV": "/dex-dev/{dataStream}-{route}/{year}/{month}/{day}/{filename}", + "TEST": "/dex-test/{dataStream}-{route}/{year}/{month}/{day}/{filename}", + "STAGE": "/dex/{dataStream}-{route}/{year}/{month}/{day}/{filename}" + } + } + ] }, { - "version": "2.0", - "data_stream_id": "ehdi", - "data_stream_route": "csv", - "received_filename": "dex-smoke-test", - "jurisdiction": "RI", - "data_producer_id": "RI-PHA", - "sender_id": "RI-PHA", - "meta_ext_objectkey": "test-obj-key", - "meta_ext_file_timestamp": "test-timestamp", - "meta_ext_uploadid": "test-upload-id", - "meta_ext_source": "test-src", - "meta_ext_filestatus": "test-file-status" + "manifest": { + "version": "2.0", + "data_stream_id": "eicr", + "data_stream_route": "fhir", + "sender_id": "APHL", + "data_producer_id": "test-producer-id", + "jurisdiction": "test-jurisdiction", + "received_filename": "dex-smoke-test", + "meta_ext_objectkey": "test-obj-key", + "meta_ext_file_timestamp": "test-file-timestamp" + }, + "delivery_targets": [ + { + "name": "eicr", + "path_template": { + "LOCAL": "/ddphss-csels/DDTP/eICR Learning Pilot(eICRLRNG)/DEX-TST/{year}/{month}/{day}/{filename}_{uploadId}", + "DEV": "/ddphss-csels/DDTP/eICR Learning Pilot(eICRLRNG)/DEX-DEV/{year}/{month}/{day}/{filename}_{uploadId}", + "TEST": "/ddphss-csels/DDTP/eICR Learning Pilot(eICRLRNG)/DEX-TST/{year}/{month}/{day}/{filename}_{uploadId}", + "STAGE": "/ddphss-csels/DDTP/eICR Learning Pilot(eICRLRNG)/DEX-STG/{year}/{month}/{day}/{filename}_{uploadId}" + } + } + ] }, { - "version": "2.0", - "data_stream_id": "pulsenet", - "data_stream_route": "localsequencefile", - "received_filename": "dex-smoke-test", - "sender_id" : "PulseNet-App", - "data_producer_id": "test-producer-id", - "jurisdiction": "test-jurisdiction" + "manifest": { + "version": "2.0", + "data_stream_id": "ehdi", + "data_stream_route": "csv", + "received_filename": "dex-smoke-test", + "jurisdiction": "RI", + "data_producer_id": "RI-PHA", + "sender_id": "RI-PHA", + "meta_ext_objectkey": "test-obj-key", + "meta_ext_file_timestamp": "test-timestamp", + "meta_ext_uploadid": "test-upload-id", + "meta_ext_source": "test-src", + "meta_ext_filestatus": "test-file-status" + }, + "delivery_targets": [ + { + "name": "ehdi", + "path_template": { + "LOCAL": "/ddnid-ncbddd/DHDD/EHDI/DEX-TST/{year}/{month}/{day}/{filename}_{uploadId}", + "DEV": "/ddnid-ncbddd/DHDD/EHDI/DEX-DEV/{year}/{month}/{day}/{filename}_{uploadId}", + "TEST": "/ddnid-ncbddd/DHDD/EHDI/DEX-TST/{year}/{month}/{day}/{filename}_{uploadId}", + "STAGE": "/ddnid-ncbddd/DHDD/EHDI/DEX-STG/{year}/{month}/{day}/{hour}/{filename}_{uploadId}" + } + } + ] }, { - "version": "2.0", - "data_stream_id": "dextesting", - "data_stream_route": "testevent1", - "received_filename": "dex-smoke-test", - "sender_id" : "test sender", - "data_producer_id": "test-producer-id", - "jurisdiction": "test-jurisdiction" + "manifest": { + "version": "2.0", + "data_stream_id": "pulsenet", + "data_stream_route": "localsequencefile", + "received_filename": "dex-smoke-test", + "sender_id": "PulseNet-App", + "data_producer_id": "test-producer-id", + "jurisdiction": "test-jurisdiction" + }, + "delivery_targets": [ + { + "name": "edav", + "path_template": { + "LOCAL": "/upload/{dataStream}-{route}/{filename}", + "DEV": "/upload/{dataStream}-{route}/{filename}", + "TEST": "/upload/{dataStream}-{route}/{filename}", + "STAGE": "/upload/{dataStream}-{route}/{filename}" + } + } + ] }, { - "version": "2.0", - "data_stream_id": "generic-immunization", - "data_stream_route": "csv", - "sender_id": "IZGW", - "data_producer_id": "XXA", - "jurisdiction": "XXA", - "received_filename": "dex-smoke-test", - "meta_ext_objectkey": "test-obj-key", - "meta_ext_file_timestamp": "test-file-timestamp", - "meta_username": "test-user", - "meta_ext_sourceversion": "V2024-09-04", - "meta_ext_submissionperiod":"test-submissionperiod" + "manifest": { + "version": "2.0", + "data_stream_id": "generic-immunization", + "data_stream_route": "csv", + "sender_id": "IZGW", + "data_producer_id": "XXA", + "jurisdiction": "XXA", + "received_filename": "dex-smoke-test", + "meta_ext_objectkey": "test-obj-key", + "meta_ext_file_timestamp": "test-file-timestamp", + "meta_username": "test-user", + "meta_ext_sourceversion": "V2024-09-04", + "meta_ext_submissionperiod": "test-submissionperiod" + }, + "delivery_targets": [ + { + "name": "ncird", + "path_template": { + "LOCAL": "/{dataStream}-other/{year}/{month}/{day}/{filename}", + "DEV": "/{dataStream}-other/{year}/{month}/{day}/{filename}", + "TEST": "/{dataStream}-other/{year}/{month}/{day}/{filename}", + "STAGE": "/{dataStream}-other/{year}/{month}/{day}/{filename}" + } + } + ] } -] \ No newline at end of file +]