From 7d927dac5b0875abe3c057ff34f976d294fc11aa Mon Sep 17 00:00:00 2001 From: Sophie Guo Date: Wed, 27 Aug 2025 09:50:21 -0700 Subject: [PATCH 1/2] Support case insensitive for container name --- .../MySqlAccountServiceIntegrationTest.java | 101 ++++++++++++++++++ .../ambry/account/AbstractAccountService.java | 6 +- .../account/MySqlAccountServiceTest.java | 24 +++++ .../com/github/ambry/account/Account.java | 8 +- 4 files changed, 132 insertions(+), 7 deletions(-) diff --git a/ambry-account/src/integration-test/java/com/github/ambry/account/MySqlAccountServiceIntegrationTest.java b/ambry-account/src/integration-test/java/com/github/ambry/account/MySqlAccountServiceIntegrationTest.java index a0dd817fcb..dab3059014 100644 --- a/ambry-account/src/integration-test/java/com/github/ambry/account/MySqlAccountServiceIntegrationTest.java +++ b/ambry-account/src/integration-test/java/com/github/ambry/account/MySqlAccountServiceIntegrationTest.java @@ -1890,6 +1890,107 @@ public void testGetDatasetVersionOutOfRetentionCount() throws AccountServiceExce assertEquals("Mismatch on the version", version1, datasetVersionRecordList.get(0).getVersion()); } + /** + * Test case-insensitive behavior for container names. + */ + @Test + public void testCaseInsensitiveContainerNames() throws Exception { + Account testAccount = makeTestAccountWithContainer(); + Container testContainer = new ArrayList<>(testAccount.getAllContainers()).get(0); + + // Add a dataset with a container name in lowercase + Dataset dataset = new DatasetBuilder(testAccount.getName(), testContainer.getName().toLowerCase(), DATASET_NAME) + .setVersionSchema(Dataset.VersionSchema.MONOTONIC) + .build(); + mySqlAccountStore.addDataset(testAccount.getId(), testContainer.getId(), dataset); + + // Retrieve the dataset using the container name in uppercase + Dataset retrievedDataset = mySqlAccountStore.getDataset( + testAccount.getId(), + testContainer.getId(), + testAccount.getName(), + testContainer.getName().toUpperCase(), + DATASET_NAME + ); + + // Verify the dataset is retrieved successfully + assertNotNull("Dataset should be retrieved successfully with case-insensitive container name", retrievedDataset); + assertEquals("Mismatch in dataset name", DATASET_NAME, retrievedDataset.getDatasetName()); + } + + /** + * Test case-insensitive behavior for creating and retrieving containers. + */ + @Test + public void testCaseInsensitiveContainerCreationAndRetrieval() throws Exception { + // Create and add an account to the database + Account testAccount = new AccountBuilder((short) 1, "testAccount", Account.AccountStatus.ACTIVE).build(); + mySqlAccountService.updateAccounts(Collections.singletonList(testAccount)); + + // Create a container with a name in lowercase + String containerNameLowercase = "testcontainerlowercase"; + Container containerLowercase = new ContainerBuilder( + (short) -1, containerNameLowercase, Container.ContainerStatus.ACTIVE, DESCRIPTION, testAccount.getId()).build(); + mySqlAccountService.updateContainers(testAccount.getName(), Collections.singletonList(containerLowercase)); + + // Retrieve the container using the name in uppercase + Container retrievedContainerUppercase = mySqlAccountService.getContainerByName( + testAccount.getName(), containerNameLowercase.toUpperCase()); + + // Verify the container is retrieved successfully + assertNotNull("Container should be retrieved successfully with case-insensitive name", retrievedContainerUppercase); + assertEquals("Mismatch in container name", containerNameLowercase, retrievedContainerUppercase.getName()); + + // Retrieve the container using the name in mixed case + String containerNameMixedCase = "TestContainerLowerCase"; + Container retrievedContainerMixedCase = mySqlAccountService.getContainerByName( + testAccount.getName(), containerNameMixedCase); + + // Verify the container is retrieved successfully + assertNotNull("Container should be retrieved successfully with mixed-case name", retrievedContainerMixedCase); + assertEquals("Mismatch in container name", containerNameLowercase, retrievedContainerMixedCase.getName()); + } + + /** + * Test case-insensitive conflict when creating containers with the same name in different cases. + */ + @Test + public void testCaseInsensitiveContainerNameConflict() throws Exception { + // Create and add an account to the database + Account testAccount = new AccountBuilder((short) 1, "testAccount", Account.AccountStatus.ACTIVE).build(); + mySqlAccountService.updateAccounts(Collections.singletonList(testAccount)); + + // Create a container with a name in lowercase + String containerNameLowercase = "testcontainer"; + Container containerLowercase = new ContainerBuilder( + (short) -1, containerNameLowercase, Container.ContainerStatus.ACTIVE, DESCRIPTION, testAccount.getId()).build(); + mySqlAccountService.updateContainers(testAccount.getName(), Collections.singletonList(containerLowercase)); + + // Attempt to create a container with the same name in uppercase with diff setting + String containerNameUppercase = containerNameLowercase.toUpperCase(); + Container containerUppercase = + new ContainerBuilder((short) -1, containerNameUppercase, Container.ContainerStatus.ACTIVE, DESCRIPTION, + testAccount.getId()).setEncrypted(true).build(); + + try { + mySqlAccountService.updateContainers(testAccount.getName(), Collections.singletonList(containerUppercase)); + fail("Expected a conflict when creating a container with the same name in a different case"); + } catch (AccountServiceException e) { + assertEquals("Mismatch in error code", AccountServiceErrorCode.ResourceConflict, e.getErrorCode()); + } + + // Attempt to create a container with the same name in mixed case + String containerNameMixedCase = "TestContainer"; + Container containerMixedCase = new ContainerBuilder( + (short) -1, containerNameMixedCase, Container.ContainerStatus.ACTIVE, DESCRIPTION, testAccount.getId()).build(); + + mySqlAccountService.updateContainers(testAccount.getName(), Collections.singletonList(containerMixedCase)); + Container retrievedContainerUpperCase = mySqlAccountService.getContainerByName( + testAccount.getName(), containerNameUppercase); + assertEquals("Mismatch in container name", containerNameLowercase, retrievedContainerUpperCase.getName()); + + } + private Account makeTestAccountWithContainer() { Container testContainer = new ContainerBuilder((short) 1, "testContainer", Container.ContainerStatus.ACTIVE, "testContainer", diff --git a/ambry-account/src/main/java/com/github/ambry/account/AbstractAccountService.java b/ambry-account/src/main/java/com/github/ambry/account/AbstractAccountService.java index 5a6633c317..fd388f9141 100644 --- a/ambry-account/src/main/java/com/github/ambry/account/AbstractAccountService.java +++ b/ambry-account/src/main/java/com/github/ambry/account/AbstractAccountService.java @@ -160,7 +160,7 @@ public Collection updateContainers(String accountName, Collection existingUnchangedContainers = new ArrayList<>(); // create a hashmap to map the name to existing containers in account Map existingContainersInAccount = new HashMap<>(); - account.getAllContainers().forEach(c -> existingContainersInAccount.put(c.getName(), c)); + account.getAllContainers().forEach(c -> existingContainersInAccount.put(c.getName().toLowerCase(), c)); // Generate container ids for new containers int nextContainerId = account.getAllContainers() @@ -173,7 +173,7 @@ public Collection updateContainers(String accountName, Collection updateContainers(String accountName, Collection containers) { checkParentAccountIdInContainers(container); checkDuplicateContainerNameOrId(container); containerIdToContainerMap.put(container.getId(), container); - containerNameToContainerMap.put(container.getName(), container); + containerNameToContainerMap.put(container.getName().toLowerCase(), container); this.containers.add(container); } } @@ -341,10 +341,10 @@ private void checkRequiredFieldsForBuild() { */ private void checkDuplicateContainerNameOrId(Container container) { if (containerIdToContainerMap.containsKey(container.getId()) || containerNameToContainerMap.containsKey( - container.getName())) { + container.getName().toLowerCase())) { Container conflictContainer = containerIdToContainerMap.get(container.getId()); conflictContainer = - conflictContainer == null ? containerNameToContainerMap.get(container.getName()) : conflictContainer; + conflictContainer == null ? containerNameToContainerMap.get(container.getName().toLowerCase()) : conflictContainer; String errorMessage = new StringBuilder("Duplicate container id or name exists. containerId=").append(container.getId()) .append(" containerName=") From 31341867954403d0fbdc1bac21d6ff634781e34f Mon Sep 17 00:00:00 2001 From: Sophie Guo Date: Wed, 3 Sep 2025 14:37:56 -0700 Subject: [PATCH 2/2] Adding more tests --- .../account/MySqlAccountServiceTest.java | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/ambry-account/src/test/java/com/github/ambry/account/MySqlAccountServiceTest.java b/ambry-account/src/test/java/com/github/ambry/account/MySqlAccountServiceTest.java index f1ed1ef11f..39d0ffb706 100644 --- a/ambry-account/src/test/java/com/github/ambry/account/MySqlAccountServiceTest.java +++ b/ambry-account/src/test/java/com/github/ambry/account/MySqlAccountServiceTest.java @@ -605,6 +605,48 @@ public void testCaseInsensitiveContainerNames() throws Exception { mySqlAccountService.getContainerByName(testAccount.getName(), "testcontainer")); assertNotNull("Container should be retrievable with uppercase", mySqlAccountService.getContainerByName(testAccount.getName(), "TESTCONTAINER")); + assertEquals("container is not case in sensitive", + mySqlAccountService.getContainerByName(testAccount.getName(), "TESTCONTAINER").getId(), + mySqlAccountService.getContainerByName(testAccount.getName(), "testcontainer").getId()); + } + + + /** + * Test conflict when creating containers with the same name in different cases. + */ + @Test + public void testContainerNameConflictWithDifferentCases() throws Exception { + // Create an account + String accountName = "testAccount"; + Account testAccount = new AccountBuilder((short) 1, accountName, Account.AccountStatus.ACTIVE).build(); + mySqlAccountService.updateAccounts(Collections.singletonList(testAccount)); + + // Create a container with the name "TestContainer" + String containerName1 = "TestContainer"; + Container container1 = new ContainerBuilder(Container.UNKNOWN_CONTAINER_ID, containerName1, + Container.ContainerStatus.ACTIVE, "Test container description", testAccount.getId()).build(); + mySqlAccountService.updateContainers(accountName, Collections.singletonList(container1)); + + // Attempt to create another container with the name "testcontainer" with same settings + String containerName2 = "testcontainer"; + Container container2 = new ContainerBuilder(Container.UNKNOWN_CONTAINER_ID, containerName2, + Container.ContainerStatus.ACTIVE, "Another test container description", testAccount.getId()).build(); + + //should succeed as all the fields are same. + mySqlAccountService.updateContainers(accountName, Collections.singletonList(container2)); + + // Attempt to create another container with the name "testcontainer" with diff settings + String containerName3 = "testcontainer"; + Container container3 = + new ContainerBuilder(Container.UNKNOWN_CONTAINER_ID, containerName3, Container.ContainerStatus.ACTIVE, + "Another test container description with diff settings", testAccount.getId()).setEncrypted(true).build(); + + try { + mySqlAccountService.updateContainers(accountName, Collections.singletonList(container3)); + fail("Expected a conflict when creating a container with the same name in a different case"); + } catch (AccountServiceException e) { + assertEquals("Mismatch in error code", AccountServiceErrorCode.ResourceConflict, e.getErrorCode()); + } } /**