From 23a575e6973c58132fbe4c6b9704fd7b998c6556 Mon Sep 17 00:00:00 2001 From: jesselanger2 Date: Wed, 20 May 2026 08:52:27 -0400 Subject: [PATCH 1/2] Refine delete semantics in .env file processing --- docs/api.md | 5 ++-- .../service/secret/EnvFileService.java | 14 +++++++--- .../controller/SecretControllerTest.java | 2 +- .../service/secret/EnvFileServiceTest.java | 27 ++++++++++++++++++- 4 files changed, 40 insertions(+), 8 deletions(-) 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..b8ac6e7 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,31 @@ void testExecuteRejectsInvalidAction() { verifyNoInteractions(secretPartRepository, postSecretService, putSecretService, deleteSecretService); } + @Test + void testExecuteStillAllowsDeleteWithColonForBackwardCompatibility() { + when(secretPartRepository.exists(new SecretKey("user1", "Key1"))).thenReturn(true); + + ResponseEntity response = envFileService.execute("user1", "Key1=delete:ignored"); + + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertEquals("Processed .env file: 0 created, 0 updated, 1 deleted", response.getBody()); + + ArgumentCaptor deleteCaptor = ArgumentCaptor.forClass(DeleteSecretRequest.class); + verify(deleteSecretService).execute(deleteCaptor.capture()); + assertEquals("Key1", deleteCaptor.getValue().getDeleteName()); + assertEquals("user1", deleteCaptor.getValue().getUser()); + } + + @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); From 5d24d57d31f23f21acea80f6cbd86d96ff2773eb Mon Sep 17 00:00:00 2001 From: jesselanger2 Date: Wed, 20 May 2026 08:56:42 -0400 Subject: [PATCH 2/2] removed unnecessary test --- .../service/secret/EnvFileServiceTest.java | 15 --------------- 1 file changed, 15 deletions(-) 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 b8ac6e7..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 @@ -96,21 +96,6 @@ void testExecuteRejectsInvalidAction() { verifyNoInteractions(secretPartRepository, postSecretService, putSecretService, deleteSecretService); } - @Test - void testExecuteStillAllowsDeleteWithColonForBackwardCompatibility() { - when(secretPartRepository.exists(new SecretKey("user1", "Key1"))).thenReturn(true); - - ResponseEntity response = envFileService.execute("user1", "Key1=delete:ignored"); - - assertEquals(HttpStatus.OK, response.getStatusCode()); - assertEquals("Processed .env file: 0 created, 0 updated, 1 deleted", response.getBody()); - - ArgumentCaptor deleteCaptor = ArgumentCaptor.forClass(DeleteSecretRequest.class); - verify(deleteSecretService).execute(deleteCaptor.capture()); - assertEquals("Key1", deleteCaptor.getValue().getDeleteName()); - assertEquals("user1", deleteCaptor.getValue().getUser()); - } - @Test void testExecuteRejectsNewWithoutValueDelimiter() { IllegalArgumentException exception = assertThrows(IllegalArgumentException.class,