diff --git a/docs/api.md b/docs/api.md index c5ce581..fd5566f 100644 --- a/docs/api.md +++ b/docs/api.md @@ -123,10 +123,11 @@ Each non-empty line must use: ```env Key1=new:val Key2=update:val -Key3=delete:val +Key3=delete ``` -The action must be `new`, `update`, or `delete`. Keys must be unique within the file; duplicate keys fail the request before any write is attempted. +The action must be `new`, `update`, or `delete`. `new` and `update` require a value after `:`; +`delete` only needs the key name and action. Keys must be unique within the file; duplicate keys fail the request before any write is attempted. Supported request formats: diff --git a/src/main/java/edu/yu/capstone/DistributedSecretsVault/service/secret/EnvFileService.java b/src/main/java/edu/yu/capstone/DistributedSecretsVault/service/secret/EnvFileService.java index c2a2b16..8934ac7 100644 --- a/src/main/java/edu/yu/capstone/DistributedSecretsVault/service/secret/EnvFileService.java +++ b/src/main/java/edu/yu/capstone/DistributedSecretsVault/service/secret/EnvFileService.java @@ -116,11 +116,13 @@ private EnvSecretOperation parseOperationLine(String line, int lineNumber) { String actionAndValue = line.substring(equalsIndex + 1); int colonIndex = actionAndValue.indexOf(':'); - if (colonIndex < 0) { + if (colonIndex < 0 && actionAndValue.isBlank()) { throw invalidLine(lineNumber); } - String actionText = actionAndValue.substring(0, colonIndex).trim().toLowerCase(Locale.ROOT); + String actionText = colonIndex < 0 + ? actionAndValue.trim().toLowerCase(Locale.ROOT) + : actionAndValue.substring(0, colonIndex).trim().toLowerCase(Locale.ROOT); EnvAction action = switch (actionText) { case "new" -> EnvAction.NEW; case "update" -> EnvAction.UPDATE; @@ -129,13 +131,17 @@ private EnvSecretOperation parseOperationLine(String line, int lineNumber) { + ": expected new, update, or delete"); }; - String value = actionAndValue.substring(colonIndex + 1); + if (colonIndex < 0 && action != EnvAction.DELETE) { + throw invalidLine(lineNumber); + } + + String value = colonIndex < 0 ? "" : actionAndValue.substring(colonIndex + 1); return new EnvSecretOperation(key, action, value); } private IllegalArgumentException invalidLine(int lineNumber) { return new IllegalArgumentException("Invalid .env entry on line " + lineNumber - + ": expected KEY=action:value"); + + ": expected KEY=new:value, KEY=update:value, or KEY=delete"); } private void validateOperationPreconditions(String user, List operations) { diff --git a/src/test/java/edu/yu/capstone/DistributedSecretsVault/controller/SecretControllerTest.java b/src/test/java/edu/yu/capstone/DistributedSecretsVault/controller/SecretControllerTest.java index 6df70fb..de7c9b1 100644 --- a/src/test/java/edu/yu/capstone/DistributedSecretsVault/controller/SecretControllerTest.java +++ b/src/test/java/edu/yu/capstone/DistributedSecretsVault/controller/SecretControllerTest.java @@ -94,7 +94,7 @@ public void testPostSecret() throws Exception { @Test public void testPostEnvFileAsPlainText() throws Exception { - String envFile = "Key1=new:val\nKey2=update:other\nKey3=delete:ignored\n"; + String envFile = "Key1=new:val\nKey2=update:other\nKey3=delete\n"; when(envFileService.execute(eq("user1"), eq(envFile))) .thenReturn(ResponseEntity.ok("processed")); diff --git a/src/test/java/edu/yu/capstone/DistributedSecretsVault/service/secret/EnvFileServiceTest.java b/src/test/java/edu/yu/capstone/DistributedSecretsVault/service/secret/EnvFileServiceTest.java index c95cc7e..78a3e88 100644 --- a/src/test/java/edu/yu/capstone/DistributedSecretsVault/service/secret/EnvFileServiceTest.java +++ b/src/test/java/edu/yu/capstone/DistributedSecretsVault/service/secret/EnvFileServiceTest.java @@ -51,7 +51,7 @@ void testExecuteProcessesEnvFileOperations() { ResponseEntity response = envFileService.execute("user1", """ Key1=new:val Key2=update:next:with:colons - Key3=delete:ignored + Key3=delete """); assertEquals(HttpStatus.OK, response.getStatusCode()); @@ -96,6 +96,16 @@ void testExecuteRejectsInvalidAction() { verifyNoInteractions(secretPartRepository, postSecretService, putSecretService, deleteSecretService); } + @Test + void testExecuteRejectsNewWithoutValueDelimiter() { + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, + () -> envFileService.execute("user1", "Key1=new")); + + assertEquals("Invalid .env entry on line 1: expected KEY=new:value, KEY=update:value, or KEY=delete", + exception.getMessage()); + verifyNoInteractions(secretPartRepository, postSecretService, putSecretService, deleteSecretService); + } + @Test void testExecuteRejectsExistingSecretForNewOperation() { when(secretPartRepository.exists(new SecretKey("user1", "Key1"))).thenReturn(true);