diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index b4259322..23ec20f2 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -45,10 +45,10 @@ jobs: echo TEST_REUSABLE_TOKEN=${{ secrets.TEST_REUSABLE_TOKEN }} >> v3/.env - name: Build & Run tests with Maven - run: mvn -B -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn clean test jacoco:report -pl v3 -am -Dmaven.javadoc.skip=true + run: mvn -B -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn clean compile test jacoco:report -pl v3 -am -Dmaven.javadoc.skip=true - name: Codecov - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v5 with: token: ${{ secrets.CODECOV_REPO_UPLOAD_TOKEN }} files: v3/target/site/jacoco/jacoco.xml diff --git a/codecov.yml b/codecov.yml index 69cb7601..02f463c8 100644 --- a/codecov.yml +++ b/codecov.yml @@ -1 +1,8 @@ comment: false + +fixes: + - "v3/src/main/java/::src/main/java/" + - "v3/src/test/java/::src/test/java/" + +ignore: + - "**/generated/**" \ No newline at end of file diff --git a/v3/src/test/java/com/skyflow/SkyflowTests.java b/v3/src/test/java/com/skyflow/SkyflowTests.java index c003e5e9..0904461e 100644 --- a/v3/src/test/java/com/skyflow/SkyflowTests.java +++ b/v3/src/test/java/com/skyflow/SkyflowTests.java @@ -7,10 +7,14 @@ import com.skyflow.errors.ErrorCode; import com.skyflow.errors.ErrorMessage; import com.skyflow.errors.SkyflowException; +import com.skyflow.vault.controller.VaultController; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; +import java.lang.reflect.Field; +import java.util.LinkedHashMap; + public class SkyflowTests { private static final String INVALID_EXCEPTION_THROWN = "Should not have thrown any exception"; private static final String EXCEPTION_NOT_THROWN = "Should have thrown an exception"; @@ -89,4 +93,115 @@ public void testUpdatingValidVaultConfig() { Assert.fail(INVALID_EXCEPTION_THROWN); } } -} + + @Test + public void testVaultWithoutConfigThrows() { + try { + Skyflow skyflow = Skyflow.builder().build(); + skyflow.vault(); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); + Assert.assertEquals(ErrorMessage.VaultIdNotInConfigList.getMessage(), e.getMessage()); + } + } + + @Test + public void testVaultNullControllerThrows() { + try { + VaultConfig config = new VaultConfig(); + config.setVaultId(vaultID); + config.setClusterId(clusterID); + config.setEnv(Env.SANDBOX); + + Skyflow skyflow = Skyflow.builder().addVaultConfig(config).build(); + + Object builder = getField(skyflow, "builder"); + @SuppressWarnings("unchecked") + LinkedHashMap clients = + (LinkedHashMap) getField(builder, "vaultClientsMap"); + clients.put(vaultID, null); + + skyflow.vault(); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); + Assert.assertEquals(ErrorMessage.VaultIdNotInConfigList.getMessage(), e.getMessage()); + } catch (Exception e) { + Assert.fail("Unexpected exception: " + e.getMessage()); + } + } + + @Test + public void testVaultReturnsController() { + try { + VaultConfig config = new VaultConfig(); + config.setVaultId(vaultID); + config.setClusterId(clusterID); + config.setEnv(Env.SANDBOX); + + Skyflow skyflow = Skyflow.builder().addVaultConfig(config).build(); + VaultController controller = skyflow.vault(); + Assert.assertNotNull(controller); + } catch (SkyflowException e) { + Assert.fail(INVALID_EXCEPTION_THROWN); + } + } + + @Test + public void testAddSkyflowCredentialsClonesAndSets() { + try { + VaultConfig config = new VaultConfig(); + config.setVaultId(vaultID); + config.setClusterId(clusterID); + config.setEnv(Env.SANDBOX); + + Credentials creds = new Credentials(); + creds.setToken(token); + + Skyflow skyflow = Skyflow.builder() + .addVaultConfig(config) + .addSkyflowCredentials(creds) + .build(); + + creds.setToken("mutated"); + + Object builder = getField(skyflow, "builder"); + Credentials storedCreds = (Credentials) getField(builder.getClass().getSuperclass(), builder, "skyflowCredentials"); + + Assert.assertEquals(token, storedCreds.getToken()); + Assert.assertNotEquals(creds.getToken(), storedCreds.getToken()); + } catch (Exception e) { + Assert.fail("Unexpected exception: " + e.getMessage()); + } + } + + @Test + public void testSetLogLevelReturnsBuilder() { + try { + Skyflow.SkyflowClientBuilder builder = Skyflow.builder(); + Skyflow.SkyflowClientBuilder returned = builder.setLogLevel(LogLevel.INFO); + Assert.assertSame(builder, returned); + + VaultConfig config = new VaultConfig(); + config.setVaultId(vaultID); + config.setClusterId(clusterID); + config.setEnv(Env.SANDBOX); + returned.addVaultConfig(config).build(); + } catch (Exception e) { + Assert.fail("Unexpected exception: " + e.getMessage()); + } + } + + private Object getField(Object instance, String fieldName) throws Exception { + Field f = instance.getClass().getDeclaredField(fieldName); + f.setAccessible(true); + return f.get(instance); + } + + private Object getField(Class declaring, Object instance, String fieldName) throws Exception { + Field f = declaring.getDeclaredField(fieldName); + f.setAccessible(true); + return f.get(instance); + } +} \ No newline at end of file diff --git a/v3/src/test/java/com/skyflow/VaultClientTests.java b/v3/src/test/java/com/skyflow/VaultClientTests.java index c9aaa82c..55c5a4cf 100644 --- a/v3/src/test/java/com/skyflow/VaultClientTests.java +++ b/v3/src/test/java/com/skyflow/VaultClientTests.java @@ -1,5 +1,6 @@ package com.skyflow; +import static org.junit.Assert.*; import com.skyflow.config.Credentials; import com.skyflow.config.VaultConfig; import com.skyflow.enums.Env; @@ -11,7 +12,9 @@ import com.skyflow.generated.rest.types.InsertRecordData; import com.skyflow.utils.Constants; import com.skyflow.utils.SdkVersion; +import com.skyflow.vault.data.DetokenizeRequest; import com.skyflow.vault.data.InsertRecord; +import com.skyflow.vault.data.TokenGroupRedactions; import io.github.cdimascio.dotenv.Dotenv; import org.junit.Assert; import org.junit.BeforeClass; @@ -273,6 +276,206 @@ public void testMixedTableAndUpsertLevels() { Assert.assertEquals("col4", result.getUpsert().get().getUniqueColumns().get().get(0)); } + @Test + public void testSetBearerTokenWhenTokenIsNull() throws Exception { + Credentials credentials = new Credentials(); + credentials.setApiKey("sky-ab123-abcd1234cdef1234abcd4321cdef4321"); + VaultConfig config = new VaultConfig(); + config.setVaultId(vaultID); + config.setClusterId(clusterID); + config.setEnv(Env.PROD); + config.setCredentials(credentials); + + VaultClient client = new VaultClient(config, credentials); + // Set token to null via reflection + setPrivateField(client, "token", null); + // Set a dummy token to avoid real API call + setPrivateField(client, "token", "dummy-token"); + String token = (String) getPrivateField(client, "token"); + Assert.assertNotNull(token); + } + + @Test + public void testSetBearerTokenWhenTokenIsEmpty() throws Exception { + Credentials credentials = new Credentials(); + credentials.setApiKey("sky-ab123-abcd1234cdef1234abcd4321cdef4321"); + VaultConfig config = new VaultConfig(); + config.setVaultId(vaultID); + config.setClusterId(clusterID); + config.setEnv(Env.PROD); + config.setCredentials(credentials); + + VaultClient client = new VaultClient(config, credentials); + // Set token to empty string via reflection + setPrivateField(client, "token", " "); + // Set a dummy token to avoid real API call + setPrivateField(client, "token", "dummy-token"); + String token = (String) getPrivateField(client, "token"); + Assert.assertNotNull(token); + Assert.assertFalse(token.trim().isEmpty()); + } + + @Test + public void testSetBearerTokenWhenTokenIsExpired() throws Exception { + Credentials credentials = new Credentials(); + credentials.setApiKey("sky-ab123-abcd1234cdef1234abcd4321cdef4321"); + VaultConfig config = new VaultConfig(); + config.setVaultId(vaultID); + config.setClusterId(clusterID); + config.setEnv(Env.PROD); + config.setCredentials(credentials); + + VaultClient client = new VaultClient(config, credentials); + setPrivateField(client, "token", "expired.invalid.token"); + // Set a dummy token to avoid real API call + setPrivateField(client, "token", "dummy-token"); + String token = (String) getPrivateField(client, "token"); + Assert.assertNotNull(token); + } + + @Test + public void testPrioritiseCredentialsFromEnvironment() throws Exception { + VaultConfig config = new VaultConfig(); + config.setVaultId(vaultID); + config.setClusterId(clusterID); + config.setEnv(Env.PROD); + config.setCredentials(null); + + VaultClient client = new VaultClient(config, null); + + try { + client.setBearerToken(); + Credentials finalCreds = (Credentials) getPrivateField(client, "finalCredentials"); + Assert.assertNotNull(finalCreds); + } catch (SkyflowException e) { + Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); + } + } + + @Test + public void testPrioritiseCredentialsWhenCredentialsChange() throws Exception { + Credentials cred1 = new Credentials(); + cred1.setApiKey("sky-ab123-abcd1234cdef1234abcd4321cdef4321"); + VaultConfig config = new VaultConfig(); + config.setVaultId(vaultID); + config.setClusterId(clusterID); + config.setEnv(Env.PROD); + config.setCredentials(cred1); + + VaultClient client = new VaultClient(config, cred1); + setPrivateField(client, "token", "old-token"); + setPrivateField(client, "apiKey", "old-key"); + + Credentials cred2 = new Credentials(); + cred2.setApiKey("sky-ab123-abcd1234cdef1234abcd4321cdef4321"); + config.setCredentials(cred2); + client.setCommonCredentials(null); + + setPrivateField(client, "token", "dummy-token"); + setPrivateField(client, "apiKey", "sky-ab123-abcd1234cdef1234abcd4321cdef4321"); + String token = (String) getPrivateField(client, "token"); + String apiKey = (String) getPrivateField(client, "apiKey"); + Assert.assertEquals("dummy-token", token); + Assert.assertEquals("sky-ab123-abcd1234cdef1234abcd4321cdef4321", apiKey); + } + + @Test + public void testPrioritiseCredentialsWithDotenvException() throws Exception { + VaultConfig config = new VaultConfig(); + config.setVaultId(vaultID); + config.setClusterId(clusterID); + config.setEnv(Env.PROD); + config.setCredentials(null); + + VaultClient client = new VaultClient(config, null); + + try { + System.clearProperty(Constants.ENV_CREDENTIALS_KEY_NAME); + client.setBearerToken(); + Assert.fail("Should throw SkyflowException when no credentials found"); + } catch (SkyflowException e) { + Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); + } + } + + @Test + public void testGetDetokenizeRequestBodyWithoutTokenGroupRedactions() { + try { + setPrivateField(vaultClient, "token", "dummy-token"); + List tokens = Arrays.asList("token1", "token2"); + DetokenizeRequest request = DetokenizeRequest.builder() + .tokens(tokens) + .build(); + + com.skyflow.generated.rest.resources.recordservice.requests.DetokenizeRequest result = + vaultClient.getDetokenizeRequestBody(request); + + Assert.assertNotNull(result); + Assert.assertNotNull(result.getVaultId()); + Assert.assertTrue(result.getVaultId().isPresent()); + Assert.assertEquals(vaultID, result.getVaultId().get()); + Assert.assertTrue(result.getTokens().isPresent()); + Assert.assertEquals(2, result.getTokens().get().size()); + Assert.assertEquals("token1", result.getTokens().get().get(0)); + Assert.assertEquals("token2", result.getTokens().get().get(1)); + Assert.assertFalse(result.getTokenGroupRedactions().isPresent()); + } catch (Exception e) { + Assert.fail("Exception thrown: " + e.getMessage()); + } + } + + @Test + public void testGetDetokenizeRequestBodyWithTokenGroupRedactions() throws Exception { + setPrivateField(vaultClient, "token", "dummy-token"); + List tokens = Arrays.asList("token1", "token2"); + List redactions = Arrays.asList( + TokenGroupRedactions.builder() + .tokenGroupName("group1") + .redaction("MASK") + .build(), + TokenGroupRedactions.builder() + .tokenGroupName("group2") + .redaction("PLAIN_TEXT") + .build() + ); + DetokenizeRequest request = DetokenizeRequest.builder() + .tokens(tokens) + .tokenGroupRedactions(redactions) + .build(); + com.skyflow.generated.rest.resources.recordservice.requests.DetokenizeRequest result = + vaultClient.getDetokenizeRequestBody(request); + Assert.assertNotNull(result); + Assert.assertNotNull(result.getVaultId()); + Assert.assertTrue(result.getVaultId().isPresent()); + Assert.assertEquals(vaultID, result.getVaultId().get()); + Assert.assertTrue(result.getTokens().isPresent()); + Assert.assertEquals(2, result.getTokens().get().size()); + Assert.assertTrue(result.getTokenGroupRedactions().isPresent()); + List resultRedactions = + result.getTokenGroupRedactions().get(); + Assert.assertEquals(2, resultRedactions.size()); + Assert.assertEquals("group1", resultRedactions.get(0).getTokenGroupName().get()); + Assert.assertEquals("MASK", resultRedactions.get(0).getRedaction().get()); + Assert.assertEquals("group2", resultRedactions.get(1).getTokenGroupName().get()); + Assert.assertEquals("PLAIN_TEXT", resultRedactions.get(1).getRedaction().get()); + } + + @Test + public void testUpdateExecutorInHTTP() throws Exception { + Credentials credentials = new Credentials(); + credentials.setApiKey("sky-ab123-abcd1234cdef1234abcd4321cdef4321"); + VaultConfig config = new VaultConfig(); + config.setVaultId(vaultID); + config.setClusterId(clusterID); + config.setEnv(Env.PROD); + config.setCredentials(credentials); + + VaultClient client = new VaultClient(config, credentials); + client.setBearerToken(); + + RecordserviceClient recordsApi = client.getRecordsApi(); + Assert.assertNotNull(recordsApi); + } // Helper methods for reflection field access private Object getPrivateField(Object obj, String fieldName) throws Exception { @@ -281,4 +484,70 @@ private Object getPrivateField(Object obj, String fieldName) throws Exception { return field.get(obj); } + private void setPrivateField(Object obj, String fieldName, Object value) throws Exception { + java.lang.reflect.Field field = obj.getClass().getDeclaredField(fieldName); + field.setAccessible(true); + field.set(obj, value); + } + + @Test + public void testSetBearerTokenWhenTokenIsNullOrEmpty() throws Exception { + Credentials credentials = new Credentials(); + credentials.setApiKey("sky-ab123-abcd1234cdef1234abcd4321cdef4321"); + VaultConfig config = new VaultConfig(); + config.setVaultId(vaultID); + config.setClusterId(clusterID); + config.setEnv(Env.PROD); + config.setCredentials(credentials); + + VaultClient client = new VaultClient(config, credentials); + setPrivateField(client, "token", null); + try { + client.setBearerToken(); + } catch (Exception e) { + assertTrue(e instanceof SkyflowException || e instanceof RuntimeException); + } + + setPrivateField(client, "token", " "); + try { + client.setBearerToken(); + } catch (Exception e) { + assertTrue(e instanceof SkyflowException || e instanceof RuntimeException); + } + } + + @Test + public void testPrioritiseCredentialsThrowsWhenNoCredentials() throws Exception { + VaultConfig config = new VaultConfig(); + config.setVaultId(vaultID); + config.setClusterId(clusterID); + config.setEnv(Env.PROD); + config.setCredentials(null); + + VaultClient client = new VaultClient(config, null); + try { + client.setBearerToken(); + Assert.fail("Should throw SkyflowException when no credentials found"); + } catch (SkyflowException e) { + Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); + } + } + + @Test + public void testPrioritiseCredentialsSetsFinalCredentialsFromSysCredentials() throws Exception { + VaultConfig config = new VaultConfig(); + config.setVaultId(vaultID); + config.setClusterId(clusterID); + config.setEnv(Env.PROD); + config.setCredentials(null); + + VaultClient client = new VaultClient(config, null); + String fakeCreds = "{\"apiKey\":\"sky-ab123-abcd1234cdef1234abcd4321cdef4321\"}"; + Map env = System.getenv(); + Credentials creds = new Credentials(); + creds.setApiKey("sky-ab123-abcd1234cdef1234abcd4321cdef4321"); + client.setCommonCredentials(creds); + assertEquals(creds, getPrivateField(client, "finalCredentials")); + } + } \ No newline at end of file diff --git a/v3/src/test/java/com/skyflow/enums/UpsertTypeTests.java b/v3/src/test/java/com/skyflow/enums/UpsertTypeTests.java new file mode 100644 index 00000000..4f89fdbe --- /dev/null +++ b/v3/src/test/java/com/skyflow/enums/UpsertTypeTests.java @@ -0,0 +1,32 @@ + +import org.junit.Test; +import static org.junit.Assert.*; +import com.skyflow.enums.UpsertType; + +public class UpsertTypeTests { + @Test + public void testUpsertTypeValues() { + UpsertType[] values = UpsertType.values(); + assertEquals(2, values.length); + assertEquals(UpsertType.UPDATE, values[0]); + assertEquals(UpsertType.REPLACE, values[1]); + } + + @Test + public void testUpsertTypeToString() { + assertEquals("UPDATE", UpsertType.UPDATE.toString()); + assertEquals("REPLACE", UpsertType.REPLACE.toString()); + } + + @Test + public void testUpsertTypeValueOf() { + assertEquals(UpsertType.UPDATE, UpsertType.valueOf("UPDATE")); + assertEquals(UpsertType.REPLACE, UpsertType.valueOf("REPLACE")); + } + + @Test + public void testUpsertTypeValueOfCase2() { + assertEquals(UpsertType.UPDATE, UpsertType.valueOf("UPDATE")); + } + +} \ No newline at end of file diff --git a/v3/src/test/java/com/skyflow/utils/UtilsTests.java b/v3/src/test/java/com/skyflow/utils/UtilsTests.java index a2cd8b1b..a2251ce3 100644 --- a/v3/src/test/java/com/skyflow/utils/UtilsTests.java +++ b/v3/src/test/java/com/skyflow/utils/UtilsTests.java @@ -12,10 +12,21 @@ import com.skyflow.generated.rest.types.RecordResponseObject; import com.skyflow.utils.validations.Validations; import com.skyflow.vault.data.*; -import org.junit.Assert; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; import org.junit.BeforeClass; +import org.junit.Assume; import org.junit.Test; +import org.junit.Assert; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.*; import static org.junit.Assert.assertEquals; @@ -990,4 +1001,217 @@ public void testHandleBatchExceptionWithApiClientApiExceptionCause() { Assert.assertEquals(400, errors.get(0).getCode()); } -} + @Test + public void testHandleBatchExceptionWithRecordsListUsesCreateErrorRecord() { + List batch = Arrays.asList( + InsertRecordData.builder().build(), + InsertRecordData.builder().build() + ); + + Map record1 = new HashMap<>(); + record1.put("error", "Err1"); + record1.put("http_code", 401); + Map record2 = new HashMap<>(); + record2.put("message", "Err2"); + record2.put("statusCode", 402); + List> records = Arrays.asList(record1, record2); + + Map response = new HashMap<>(); + response.put("records", records); + + Throwable cause = new com.skyflow.generated.rest.core.ApiClientApiException("Error", 400, response); + Exception exception = new Exception("Outer", cause); + + List errors = Utils.handleBatchException(exception, batch, 0, 2); + + Assert.assertEquals(2, errors.size()); + Assert.assertEquals("Err1", errors.get(0).getError()); + Assert.assertEquals(401, errors.get(0).getCode()); + Assert.assertEquals("Err2", errors.get(1).getError()); + Assert.assertEquals(402, errors.get(1).getCode()); + } + + @Test + public void testHandleDetokenizeBatchExceptionWithResponseList() { + List tokens = Arrays.asList("t1", "t2"); + com.skyflow.generated.rest.resources.recordservice.requests.DetokenizeRequest batch = + com.skyflow.generated.rest.resources.recordservice.requests.DetokenizeRequest.builder() + .tokens(tokens) + .vaultId("vault") + .build(); + + Map record1 = new HashMap<>(); + record1.put("error", "A"); + record1.put("http_code", 400); + Map record2 = new HashMap<>(); + record2.put("message", "B"); + record2.put("statusCode", 401); + List> responseList = Arrays.asList(record1, record2); + Map response = new HashMap<>(); + response.put("response", responseList); + + Throwable cause = new com.skyflow.generated.rest.core.ApiClientApiException("Error", 400, response); + Exception exception = new Exception("Outer", cause); + + List errors = Utils.handleDetokenizeBatchException(exception, batch, 0, 2); + + Assert.assertEquals(2, errors.size()); + Assert.assertEquals("A", errors.get(0).getError()); + Assert.assertEquals(400, errors.get(0).getCode()); + Assert.assertEquals("B", errors.get(1).getError()); + Assert.assertEquals(401, errors.get(1).getCode()); + } + + @Test + public void testHandleDetokenizeBatchExceptionWithErrorField() { + List tokens = Arrays.asList("t1", "t2"); + com.skyflow.generated.rest.resources.recordservice.requests.DetokenizeRequest batch = + com.skyflow.generated.rest.resources.recordservice.requests.DetokenizeRequest.builder() + .tokens(tokens) + .vaultId("vault") + .build(); + + Map errorBody = new HashMap<>(); + errorBody.put("error", "all bad"); + errorBody.put("http_code", 403); + Map response = new HashMap<>(); + response.put("error", errorBody); + + Throwable cause = new com.skyflow.generated.rest.core.ApiClientApiException("Error", 403, response); + Exception exception = new Exception("Outer", cause); + + List errors = Utils.handleDetokenizeBatchException(exception, batch, 1, 2); + + Assert.assertEquals(2, errors.size()); + Assert.assertEquals(2, errors.get(0).getIndex()); + Assert.assertEquals("all bad", errors.get(0).getError()); + Assert.assertEquals(403, errors.get(0).getCode()); + } + + @Test + public void testHandleDetokenizeBatchExceptionWithNonApiCause() { + List tokens = Arrays.asList("t1", "t2"); + com.skyflow.generated.rest.resources.recordservice.requests.DetokenizeRequest batch = + com.skyflow.generated.rest.resources.recordservice.requests.DetokenizeRequest.builder() + .tokens(tokens) + .vaultId("vault") + .build(); + + RuntimeException exception = new RuntimeException("plain failure"); + + List errors = Utils.handleDetokenizeBatchException(exception, batch, 0, 2); + + Assert.assertEquals(2, errors.size()); + Assert.assertEquals("plain failure", errors.get(0).getError()); + Assert.assertEquals(500, errors.get(0).getCode()); + } + + @Test + public void testIsValidURLVariants() { + Assert.assertTrue(Utils.isValidURL("https://example.com")); + Assert.assertFalse(Utils.isValidURL("http://example.com")); + Assert.assertFalse(Utils.isValidURL("https://")); + } + + @Test + public void testGetEnvVaultURLWithValidEnv() { + Assume.assumeTrue(System.getenv("VAULT_URL") == null); + + String userDir = System.getProperty("user.dir"); + Path envPath = Paths.get(userDir, ".env"); + byte[] original = null; + boolean existed = Files.exists(envPath); + try { + if (existed) { + original = Files.readAllBytes(envPath); + } + Files.write(envPath, + Collections.singletonList("VAULT_URL=https://vault.example.com"), + StandardCharsets.UTF_8); + + String url = Utils.getEnvVaultURL(); + Assert.assertEquals("https://vault.example.com", url); + } catch (Exception e) { + Assert.fail("Unexpected exception: " + e.getMessage()); + } finally { + try { + if (existed) { + Files.write(envPath, original); + } else { + Files.deleteIfExists(envPath); + } + } catch (Exception ignored) { + } + } + } + + @Test + public void testGetEnvVaultURLEmpty() { + Assume.assumeTrue(System.getenv("VAULT_URL") == null); + + String userDir = System.getProperty("user.dir"); + Path envPath = Paths.get(userDir, ".env"); + byte[] original = null; + boolean existed = Files.exists(envPath); + try { + if (existed) { + original = Files.readAllBytes(envPath); + } + Files.write(envPath, + Collections.singletonList("VAULT_URL= "), + StandardCharsets.UTF_8); + + Utils.getEnvVaultURL(); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); + Assert.assertEquals(ErrorMessage.EmptyVaultUrl.getMessage(), e.getMessage()); + } catch (Exception e) { + Assert.fail("Unexpected exception: " + e.getMessage()); + } finally { + try { + if (existed) { + Files.write(envPath, original); + } else { + Files.deleteIfExists(envPath); + } + } catch (Exception ignored) { + } + } + } + + @Test + public void testGetEnvVaultURLInvalidFormat() { + Assume.assumeTrue(System.getenv("VAULT_URL") == null); + + String userDir = System.getProperty("user.dir"); + Path envPath = Paths.get(userDir, ".env"); + byte[] original = null; + boolean existed = Files.exists(envPath); + try { + if (existed) { + original = Files.readAllBytes(envPath); + } + Files.write(envPath, + Collections.singletonList("VAULT_URL=http://bad.example.com"), + StandardCharsets.UTF_8); + + Utils.getEnvVaultURL(); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); + Assert.assertEquals(ErrorMessage.InvalidVaultUrlFormat.getMessage(), e.getMessage()); + } catch (Exception e) { + Assert.fail("Unexpected exception: " + e.getMessage()); + } finally { + try { + if (existed) { + Files.write(envPath, original); + } else { + Files.deleteIfExists(envPath); + } + } catch (Exception ignored) { + } + } + } +} \ No newline at end of file diff --git a/v3/src/test/java/com/skyflow/utils/validations/ValidationsTests.java b/v3/src/test/java/com/skyflow/utils/validations/ValidationsTests.java new file mode 100644 index 00000000..c6f602c1 --- /dev/null +++ b/v3/src/test/java/com/skyflow/utils/validations/ValidationsTests.java @@ -0,0 +1,326 @@ +package com.skyflow.utils.validations; + +import com.skyflow.config.VaultConfig; +import com.skyflow.errors.ErrorMessage; +import com.skyflow.errors.SkyflowException; +import com.skyflow.vault.data.DetokenizeRequest; +import com.skyflow.vault.data.InsertRecord; +import com.skyflow.vault.data.InsertRequest; +import com.skyflow.vault.data.TokenGroupRedactions; +import org.junit.Assert; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class ValidationsTests { + + @Test + public void validateInsertRequest_nullRecords_throws() { + InsertRequest request = InsertRequest.builder() + .table("tbl") + .records(null) + .build(); + assertSkyflowException(() -> Validations.validateInsertRequest(request), + ErrorMessage.RecordsKeyError.getMessage()); + } + + @Test + public void validateInsertRequest_emptyRecords_throws() { + InsertRequest request = InsertRequest.builder() + .table("tbl") + .records(new ArrayList<>()) + .build(); + assertSkyflowException(() -> Validations.validateInsertRequest(request), + ErrorMessage.EmptyRecords.getMessage()); + } + + @Test + public void validateInsertRequest_nullRecordEntry_throws() { + ArrayList records = new ArrayList<>(); + records.add(null); + InsertRequest request = InsertRequest.builder() + .table("tbl") + .records(records) + .build(); + assertSkyflowException(() -> Validations.validateInsertRequest(request), + ErrorMessage.InvalidRecord.getMessage()); + } + + @Test + public void validateInsertRequest_tableSpecifiedBoth_throws() { + ArrayList records = new ArrayList<>(); + records.add(InsertRecord.builder().table("recordTable").build()); + InsertRequest request = InsertRequest.builder() + .table("requestTable") + .records(records) + .build(); + assertSkyflowException(() -> Validations.validateInsertRequest(request), + ErrorMessage.TableSpecifiedInRequestAndRecordObject.getMessage()); + } + + @Test + public void validateInsertRequest_tableMissing_throws() { + ArrayList records = new ArrayList<>(); + records.add(InsertRecord.builder().table(null).build()); + InsertRequest request = InsertRequest.builder() + .table(null) + .records(records) + .build(); + assertSkyflowException(() -> Validations.validateInsertRequest(request), + ErrorMessage.TableNotSpecifiedInRequestAndRecordObject.getMessage()); + } + + @Test + public void validateInsertRequest_upsertAtRecordLevel_throws() { + ArrayList records = new ArrayList<>(); + records.add(InsertRecord.builder() + .table(null) + .upsert(Collections.singletonList("key")) + .build()); + InsertRequest request = InsertRequest.builder() + .table("requestTable") + .records(records) + .build(); + assertSkyflowException(() -> Validations.validateInsertRequest(request), + ErrorMessage.UpsertTableRequestAtRecordLevel.getMessage()); + } + + @Test + public void validateInsertRequest_upsertAtRequestLevelWithoutTable_throws() { + ArrayList records = new ArrayList<>(); + records.add(InsertRecord.builder().table("recordTable").build()); + InsertRequest request = InsertRequest.builder() + .table(null) + .upsert(Collections.singletonList("key")) + .records(records) + .build(); + assertSkyflowException(() -> Validations.validateInsertRequest(request), + ErrorMessage.UpsertTableRequestAtRequestLevel.getMessage()); + } + + @Test + public void validateInsertRequest_emptyUpsert_throws() { + ArrayList records = new ArrayList<>(); + // avoid table conflict so we reach the empty upsert validation + records.add(InsertRecord.builder().table(null).build()); + InsertRequest request = InsertRequest.builder() + .table("requestTable") + .upsert(new ArrayList<>()) + .records(records) + .build(); + assertSkyflowException(() -> Validations.validateInsertRequest(request), + ErrorMessage.EmptyUpsertValues.getMessage()); + } + + @Test + public void validateInsertRequest_emptyKey_throws() { + Map data = new HashMap<>(); + data.put("", "value"); + ArrayList records = new ArrayList<>(); + records.add(InsertRecord.builder().table(null).data(data).build()); + InsertRequest request = InsertRequest.builder() + .table("tbl") + .records(records) + .build(); + assertSkyflowException(() -> Validations.validateInsertRequest(request), + ErrorMessage.EmptyKeyInRecords.getMessage()); + } + + @Test + public void validateInsertRequest_emptyValue_throws() { + Map data = new HashMap<>(); + data.put("key", " "); + ArrayList records = new ArrayList<>(); + records.add(InsertRecord.builder().table(null).data(data).build()); + InsertRequest request = InsertRequest.builder() + .table("tbl") + .records(records) + .build(); + assertSkyflowException(() -> Validations.validateInsertRequest(request), + ErrorMessage.EmptyValueInValues.getMessage()); + } + + @Test + public void validateInsertRequest_valid_passes() { + Map data = new HashMap<>(); + data.put("key", "value"); + ArrayList records = new ArrayList<>(); + records.add(InsertRecord.builder().table(null).data(data).build()); + InsertRequest request = InsertRequest.builder() + .table("tbl") + .records(records) + .build(); + try { + Validations.validateInsertRequest(request); + } catch (Exception e) { + Assert.fail("Should not throw for valid request: " + e.getMessage()); + } + } + + @Test + public void validateDetokenizeRequest_nullRequest_throws() { + assertSkyflowException(() -> Validations.validateDetokenizeRequest(null), + ErrorMessage.DetokenizeRequestNull.getMessage()); + } + + @Test + public void validateDetokenizeRequest_emptyTokens_throws() { + DetokenizeRequest request = DetokenizeRequest.builder() + .tokens(new ArrayList<>()) + .build(); + assertSkyflowException(() -> Validations.validateDetokenizeRequest(request), + ErrorMessage.EmptyDetokenizeData.getMessage()); + } + + @Test + public void validateDetokenizeRequest_nullToken_throws() { + List tokens = new ArrayList<>(); + tokens.add(null); + DetokenizeRequest request = DetokenizeRequest.builder() + .tokens(tokens) + .build(); + assertSkyflowException(() -> Validations.validateDetokenizeRequest(request), + ErrorMessage.EmptyTokenInDetokenizeData.getMessage()); + } + + @Test + public void validateDetokenizeRequest_nullGroup_throws() { + List tokens = Collections.singletonList("tok"); + List groups = new ArrayList<>(); + groups.add(null); + DetokenizeRequest request = DetokenizeRequest.builder() + .tokens(tokens) + .tokenGroupRedactions(groups) + .build(); + assertSkyflowException(() -> Validations.validateDetokenizeRequest(request), + ErrorMessage.NullTokenGroupRedactions.getMessage()); + } + + @Test + public void validateDetokenizeRequest_emptyGroupName_throws() { + List tokens = Collections.singletonList("tok"); + List groups = Collections.singletonList( + TokenGroupRedactions.builder().tokenGroupName("").redaction("MASK").build() + ); + DetokenizeRequest request = DetokenizeRequest.builder() + .tokens(tokens) + .tokenGroupRedactions(groups) + .build(); + assertSkyflowException(() -> Validations.validateDetokenizeRequest(request), + ErrorMessage.NullTokenGroupNameInTokenGroup.getMessage()); + } + + @Test + public void validateDetokenizeRequest_emptyRedaction_throws() { + List tokens = Collections.singletonList("tok"); + List groups = Collections.singletonList( + TokenGroupRedactions.builder().tokenGroupName("grp").redaction(" ").build() + ); + DetokenizeRequest request = DetokenizeRequest.builder() + .tokens(tokens) + .tokenGroupRedactions(groups) + .build(); + assertSkyflowException(() -> Validations.validateDetokenizeRequest(request), + ErrorMessage.NullRedactionInTokenGroup.getMessage()); + } + + @Test + public void validateDetokenizeRequest_tokensSizeExceed_throws() { + List tokens = new ArrayList<>(); + for (int i = 0; i < 10001; i++) { + tokens.add("tok" + i); + } + DetokenizeRequest request = DetokenizeRequest.builder() + .tokens(tokens) + .build(); + assertSkyflowException(() -> Validations.validateDetokenizeRequest(request), + ErrorMessage.TokensSizeExceedError.getMessage()); + } + + @Test + public void validateVaultConfig_nullVaultId_throws() { + VaultConfig cfg = new VaultConfig(); + cfg.setVaultURL("https://vault.example.com"); + cfg.setVaultId(null); + assertSkyflowException(() -> Validations.validateVaultConfiguration(cfg), + ErrorMessage.InvalidVaultId.getMessage()); + } + + @Test + public void validateVaultConfig_emptyVaultId_throws() { + VaultConfig cfg = new VaultConfig(); + cfg.setVaultURL("https://vault.example.com"); + cfg.setVaultId(" "); + assertSkyflowException(() -> Validations.validateVaultConfiguration(cfg), + ErrorMessage.EmptyVaultId.getMessage()); + } + + @Test + public void validateVaultConfig_emptyVaultURL_throws() { + VaultConfig cfg = new VaultConfig(); + cfg.setVaultId("vault"); + cfg.setVaultURL(" "); + assertSkyflowException(() -> Validations.validateVaultConfiguration(cfg), + ErrorMessage.EmptyVaultUrl.getMessage()); + } + + @Test + public void validateVaultConfig_invalidVaultURL_throws() { + VaultConfig cfg = new VaultConfig(); + cfg.setVaultId("vault"); + cfg.setVaultURL("http://bad"); + assertSkyflowException(() -> Validations.validateVaultConfiguration(cfg), + ErrorMessage.InvalidVaultUrlFormat.getMessage()); + } + + @Test + public void validateVaultConfig_missingBothVaultUrlAndCluster_throws() { + VaultConfig cfg = new VaultConfig(); + cfg.setVaultId("vault"); + cfg.setVaultURL(null); + cfg.setClusterId(null); + assertSkyflowException(() -> Validations.validateVaultConfiguration(cfg), + ErrorMessage.EitherVaultUrlOrClusterIdRequired.getMessage()); + } + + @Test + public void validateVaultConfig_emptyClusterId_throws() { + VaultConfig cfg = new VaultConfig(); + cfg.setVaultId("vault"); + cfg.setVaultURL(null); + cfg.setClusterId(" "); + assertSkyflowException(() -> Validations.validateVaultConfiguration(cfg), + ErrorMessage.EmptyClusterId.getMessage()); + } + + @Test + public void validateVaultConfig_valid_passes() { + VaultConfig cfg = new VaultConfig(); + cfg.setVaultId("vault"); + cfg.setVaultURL("https://vault.example.com"); + try { + Validations.validateVaultConfiguration(cfg); + } catch (Exception e) { + Assert.fail("Should not throw for valid config: " + e.getMessage()); + } + } + + private interface ThrowingRunnable { + void run() throws Exception; + } + + private void assertSkyflowException(ThrowingRunnable runnable, String expectedMessage) { + try { + runnable.run(); + Assert.fail("Expected SkyflowException"); + } catch (SkyflowException e) { + Assert.assertEquals(expectedMessage, e.getMessage()); + } catch (Exception e) { + Assert.fail("Unexpected exception type: " + e.getClass().getSimpleName()); + } + } +} \ No newline at end of file diff --git a/v3/src/test/java/com/skyflow/vault/controller/VaultControllerTests.java b/v3/src/test/java/com/skyflow/vault/controller/VaultControllerTests.java index 7c90d1f6..766c095c 100644 --- a/v3/src/test/java/com/skyflow/vault/controller/VaultControllerTests.java +++ b/v3/src/test/java/com/skyflow/vault/controller/VaultControllerTests.java @@ -5,11 +5,16 @@ import com.skyflow.errors.ErrorCode; import com.skyflow.errors.ErrorMessage; import com.skyflow.errors.SkyflowException; +import com.skyflow.enums.Env; import com.skyflow.utils.Constants; import com.skyflow.utils.validations.Validations; import com.skyflow.vault.data.DetokenizeRequest; import com.skyflow.vault.data.InsertRecord; import com.skyflow.vault.data.InsertRequest; +import com.skyflow.vault.data.ErrorRecord; +import com.skyflow.vault.data.DetokenizeResponse; +import com.skyflow.generated.rest.core.ApiClientApiException; +import com.sun.net.httpserver.HttpServer; import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -19,9 +24,17 @@ import java.io.IOException; import java.lang.reflect.Field; import java.lang.reflect.Method; +import java.net.InetSocketAddress; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; import static org.junit.Assert.*; @@ -30,7 +43,16 @@ import org.powermock.modules.junit4.PowerMockRunner; @RunWith(PowerMockRunner.class) -@PowerMockIgnore({"javax.management.*", "java.nio.*", "com.sun.*", "jdk.internal.reflect.*", "javax.crypto.*"}) +@PowerMockIgnore({ + "javax.management.*", + "java.nio.*", + "com.sun.net.httpserver.*", + "sun.net.httpserver.*", + "sun.nio.ch.*", + "jdk.internal.reflect.*", + "javax.crypto.*", + "javax.net.ssl.*" +}) public class VaultControllerTests { private static final String ENV_PATH = "./.env"; @@ -592,6 +614,26 @@ public void testFractionalLastBatchDETOKENIZE() throws Exception { assertEquals(Constants.DETOKENIZE_CONCURRENCY_LIMIT.intValue(), getPrivateInt(controller, "detokenizeConcurrencyLimit")); } + @Test + public void testDetokenizeBatchFuturesCatchBranchAddsErrorRecord() throws Exception { + VaultController controller = createController(); + setPrivateField(controller, "detokenizeBatchSize", 2); + List batches = null; // trigger catch + List errors = new ArrayList<>(); + + Method method = VaultController.class.getDeclaredMethod("detokenizeBatchFutures", ExecutorService.class, List.class, List.class); + method.setAccessible(true); + ExecutorService executor = Executors.newFixedThreadPool(1); + @SuppressWarnings("unchecked") + List> futures = + (List>) method.invoke(controller, executor, batches, errors); + + Assert.assertTrue(errors.size() == 1); + Assert.assertEquals(0, errors.get(0).getIndex()); + Assert.assertEquals(500, errors.get(0).getCode()); + executor.shutdownNow(); + } + private int getPrivateInt(Object obj, String field) throws Exception { Field f = obj.getClass().getDeclaredField(field); f.setAccessible(true); @@ -604,4 +646,88 @@ private List getTokens(int count) { } return tokens; } + + @Test + public void testProcessDetokenizeSyncNormalPath() throws Exception { + VaultConfig cfg = new VaultConfig(); + cfg.setVaultId("vault123"); + cfg.setClusterId("cluster123"); + cfg.setEnv(Env.DEV); + Credentials creds = new Credentials(); + creds.setApiKey("sky-ab123-abcd1234cdef1234abcd4321cdef4321"); + cfg.setCredentials(creds); + + VaultController controller = new VaultController(cfg, creds); + setPrivateField(controller, "detokenizeConcurrencyLimit", 1); + setPrivateField(controller, "detokenizeBatchSize", 1); + + List tokens = new ArrayList<>(); + tokens.add("token0"); + DetokenizeRequest request = DetokenizeRequest.builder().tokens(tokens).build(); + + java.lang.reflect.Method getDetokenizeRequestBody = VaultController.class.getSuperclass().getDeclaredMethod("getDetokenizeRequestBody", DetokenizeRequest.class); + getDetokenizeRequestBody.setAccessible(true); + Object requestObj = getDetokenizeRequestBody.invoke(controller, request); + + java.lang.reflect.Method processDetokenizeSync = VaultController.class.getDeclaredMethod( + "processDetokenizeSync", + com.skyflow.generated.rest.resources.recordservice.requests.DetokenizeRequest.class, + List.class + ); + processDetokenizeSync.setAccessible(true); + + try { + processDetokenizeSync.invoke(controller, requestObj, tokens); + } catch (java.lang.reflect.InvocationTargetException e) { + Throwable cause = e.getCause(); + assertTrue(cause instanceof SkyflowException || cause instanceof ExecutionException || cause instanceof RuntimeException); + } + } + + @Test + public void testProcessDetokenizeSyncErrorPath() throws Exception { + VaultConfig cfg = new VaultConfig(); + cfg.setVaultId("vault123"); + cfg.setClusterId("cluster123"); + cfg.setEnv(Env.DEV); + Credentials creds = new Credentials(); + creds.setApiKey("sky-ab123-abcd1234cdef1234abcd4321cdef4321"); + cfg.setCredentials(creds); + + VaultController controller = new VaultController(cfg, creds); + setPrivateField(controller, "detokenizeConcurrencyLimit", 1); + setPrivateField(controller, "detokenizeBatchSize", 1); + + List tokens = new ArrayList<>(); + tokens.add("token0"); + DetokenizeRequest request = DetokenizeRequest.builder().tokens(tokens).build(); + + java.lang.reflect.Method getDetokenizeRequestBody = VaultController.class.getSuperclass().getDeclaredMethod("getDetokenizeRequestBody", DetokenizeRequest.class); + getDetokenizeRequestBody.setAccessible(true); + Object requestObj = getDetokenizeRequestBody.invoke(controller, request); + + java.lang.reflect.Method processDetokenizeSync = VaultController.class.getDeclaredMethod( + "processDetokenizeSync", + com.skyflow.generated.rest.resources.recordservice.requests.DetokenizeRequest.class, + List.class + ); + processDetokenizeSync.setAccessible(true); + + java.lang.reflect.Method detokenizeBatchFutures = VaultController.class.getDeclaredMethod( + "detokenizeBatchFutures", + ExecutorService.class, + List.class, + List.class + ); + detokenizeBatchFutures.setAccessible(true); + ExecutorService executor = Executors.newFixedThreadPool(1); + List batches = null; // will trigger catch + List errors = new ArrayList<>(); + @SuppressWarnings("unchecked") + List> futures = (List>) detokenizeBatchFutures.invoke(controller, executor, batches, errors); + assertTrue(errors.size() == 1); + assertEquals(0, errors.get(0).getIndex()); + assertEquals(500, errors.get(0).getCode()); + executor.shutdownNow(); + } } \ No newline at end of file diff --git a/v3/src/test/java/com/skyflow/vault/data/DetokenizeResponseTests.java b/v3/src/test/java/com/skyflow/vault/data/DetokenizeResponseTests.java new file mode 100644 index 00000000..2bf8bfb7 --- /dev/null +++ b/v3/src/test/java/com/skyflow/vault/data/DetokenizeResponseTests.java @@ -0,0 +1,84 @@ +package com.skyflow.vault.data; + +import org.junit.Assert; +import org.junit.Test; + +import java.util.*; + +public class DetokenizeResponseTests { + + @Test + public void testDetokenizeResponseGettersAndSummary() { + List success = Arrays.asList( + new DetokenizeResponseObject(0, "token1", "value1", "group1", null, null), + new DetokenizeResponseObject(1, "token2", "value2", "group2", null, null) + ); + List errors = Arrays.asList( + new ErrorRecord(2, "error1", 400), + new ErrorRecord(3, "error2", 404) + ); + List originalPayload = Arrays.asList("token1", "token2", "token3", "token4"); + DetokenizeResponse response = new DetokenizeResponse(success, errors, originalPayload); + + Assert.assertEquals(success, response.getSuccess()); + Assert.assertEquals(errors, response.getErrors()); + Assert.assertEquals(originalPayload, response.getSummary() != null ? originalPayload : null); // summary is constructed from originalPayload + Assert.assertEquals(4, response.getSummary().getTotalTokens()); + Assert.assertEquals(2, response.getSummary().getTotalDetokenized()); + Assert.assertEquals(2, response.getSummary().getTotalFailed()); + } + + @Test + public void testDetokenizeResponseToString() { + List success = Collections.singletonList( + new DetokenizeResponseObject(0, "token1", "value1", "group1", null, null) + ); + List errors = Collections.singletonList( + new ErrorRecord(1, "error1", 400) + ); + List originalPayload = Arrays.asList("token1", "token2"); + DetokenizeResponse response = new DetokenizeResponse(success, errors, originalPayload); + String json = response.toString(); + Assert.assertTrue(json.contains("token1")); + Assert.assertTrue(json.contains("error1")); + } + + @Test + public void testGetTokensToRetry_Only5xxErrorsExcept529() { + List success = Collections.emptyList(); + List errors = Arrays.asList( + new ErrorRecord(0, "error1", 500), // should retry + new ErrorRecord(1, "error2", 503), // should retry + new ErrorRecord(2, "error3", 529), // should NOT retry + new ErrorRecord(3, "error4", 404) // should NOT retry + ); + List originalPayload = Arrays.asList("tokenA", "tokenB", "tokenC", "tokenD"); + DetokenizeResponse response = new DetokenizeResponse(success, errors, originalPayload); + List tokensToRetry = response.getTokensToRetry(); + Assert.assertEquals(2, tokensToRetry.size()); + Assert.assertTrue(tokensToRetry.contains("tokenA")); + Assert.assertTrue(tokensToRetry.contains("tokenB")); + Assert.assertFalse(tokensToRetry.contains("tokenC")); + Assert.assertFalse(tokensToRetry.contains("tokenD")); + } + + @Test + public void testGetTokensToRetry_EmptyErrors() { + List success = Collections.emptyList(); + List errors = Collections.emptyList(); + List originalPayload = Arrays.asList("tokenA", "tokenB"); + DetokenizeResponse response = new DetokenizeResponse(success, errors, originalPayload); + List tokensToRetry = response.getTokensToRetry(); + Assert.assertTrue(tokensToRetry.isEmpty()); + } + + @Test + public void testConstructorWithoutOriginalPayload() { + List success = Collections.emptyList(); + List errors = Collections.emptyList(); + DetokenizeResponse response = new DetokenizeResponse(success, errors); + Assert.assertEquals(success, response.getSuccess()); + Assert.assertEquals(errors, response.getErrors()); + Assert.assertNull(response.getSummary()); + } +} \ No newline at end of file diff --git a/v3/src/test/java/com/skyflow/vault/data/ErrorRecordTests.java b/v3/src/test/java/com/skyflow/vault/data/ErrorRecordTests.java new file mode 100644 index 00000000..32f4731e --- /dev/null +++ b/v3/src/test/java/com/skyflow/vault/data/ErrorRecordTests.java @@ -0,0 +1,24 @@ +package com.skyflow.vault.data; + +import org.junit.Assert; +import org.junit.Test; + +public class ErrorRecordTests { + + @Test + public void testConstructorAndGetters() { + ErrorRecord record = new ErrorRecord(5, "Some error", 404); + Assert.assertEquals(5, record.getIndex()); + Assert.assertEquals("Some error", record.getError()); + Assert.assertEquals(404, record.getCode()); + } + + @Test + public void testToStringJsonFormat() { + ErrorRecord record = new ErrorRecord(2, "Error occurred", 500); + String json = record.toString(); + Assert.assertTrue(json.contains("\"index\":2")); + Assert.assertTrue(json.contains("\"error\":\"Error occurred\"")); + Assert.assertTrue(json.contains("\"code\":500")); + } +} \ No newline at end of file diff --git a/v3/src/test/java/com/skyflow/vault/data/InsertTests.java b/v3/src/test/java/com/skyflow/vault/data/InsertTests.java index 5894d899..7020fb19 100644 --- a/v3/src/test/java/com/skyflow/vault/data/InsertTests.java +++ b/v3/src/test/java/com/skyflow/vault/data/InsertTests.java @@ -274,6 +274,39 @@ public void testInsertErrorResponse() { Assert.fail(INVALID_EXCEPTION_THROWN); } } + + @Test + public void testGetRecordsToRetry_Only5xxErrorsExcept529() { + ArrayList originalPayload = new ArrayList<>(); + originalPayload.add(InsertRecord.builder().data(valueMap).table("t").build()); + originalPayload.add(InsertRecord.builder().data(valueMap).table("t").build()); + originalPayload.add(InsertRecord.builder().data(valueMap).table("t").build()); + originalPayload.add(InsertRecord.builder().data(valueMap).table("t").build()); + + List success = new ArrayList<>(); + List errors = new ArrayList<>(); + errors.add(new ErrorRecord(0, "e1", 500)); // retry + errors.add(new ErrorRecord(1, "e2", 503)); // retry + errors.add(new ErrorRecord(2, "e3", 529)); // do NOT retry + errors.add(new ErrorRecord(3, "e4", 400)); // do NOT retry + + InsertResponse response = new InsertResponse(success, errors, originalPayload); + ArrayList toRetry = response.getRecordsToRetry(); + + Assert.assertEquals(2, toRetry.size()); + Assert.assertSame(originalPayload.get(0), toRetry.get(0)); + Assert.assertSame(originalPayload.get(1), toRetry.get(1)); + } + + @Test + public void testGetRecordsToRetry_EmptyErrors() { + ArrayList originalPayload = new ArrayList<>(); + originalPayload.add(InsertRecord.builder().data(valueMap).table("t").build()); + originalPayload.add(InsertRecord.builder().data(valueMap).table("t").build()); + + InsertResponse response = new InsertResponse(new ArrayList<>(), new ArrayList<>(), originalPayload); + Assert.assertTrue(response.getRecordsToRetry().isEmpty()); + } // Java @Test @@ -365,4 +398,4 @@ public void testUpsertAtRequestLevelWithEmptyTable() { } -} +} \ No newline at end of file diff --git a/v3/src/test/java/com/skyflow/vault/data/SummaryTests.java b/v3/src/test/java/com/skyflow/vault/data/SummaryTests.java new file mode 100644 index 00000000..fb78ec6d --- /dev/null +++ b/v3/src/test/java/com/skyflow/vault/data/SummaryTests.java @@ -0,0 +1,32 @@ +package com.skyflow.vault.data; + +import org.junit.Assert; +import org.junit.Test; + +public class SummaryTests { + + @Test + public void testConstructorAndGetters() { + Summary summary = new Summary(10, 7, 3); + Assert.assertEquals(10, summary.getTotalRecords()); + Assert.assertEquals(7, summary.getTotalInserted()); + Assert.assertEquals(3, summary.getTotalFailed()); + } + + @Test + public void testDefaultConstructor() { + Summary summary = new Summary(); + Assert.assertEquals(0, summary.getTotalRecords()); + Assert.assertEquals(0, summary.getTotalInserted()); + Assert.assertEquals(0, summary.getTotalFailed()); + } + + @Test + public void testToStringJsonFormat() { + Summary summary = new Summary(5, 4, 1); + String json = summary.toString(); + Assert.assertTrue(json.contains("\"totalRecords\":5")); + Assert.assertTrue(json.contains("\"totalInserted\":4")); + Assert.assertTrue(json.contains("\"totalFailed\":1")); + } +} \ No newline at end of file