diff --git a/app/proprietary/src/test/java/stirling/software/proprietary/util/SecretMaskerTest.java b/app/proprietary/src/test/java/stirling/software/proprietary/util/SecretMaskerTest.java
new file mode 100644
index 000000000..b720cd043
--- /dev/null
+++ b/app/proprietary/src/test/java/stirling/software/proprietary/util/SecretMaskerTest.java
@@ -0,0 +1,178 @@
+package stirling.software.proprietary.util;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit tests for {@link SecretMasker}.
+ *
+ *
Assumptions:
+ * - Key matching is case-insensitive via the pattern in SENSITIVE.
+ * - If the key matches a sensitive pattern, the value is replaced with "***REDACTED***".
+ * - Nested maps and lists are searched recursively.
+ * - Null maps and null values are ignored or returned as null.
+ * - Non-sensitive keys/values remain unchanged.
+ */
+class SecretMaskerTest {
+
+ @Nested
+ @DisplayName("mask(Map) method")
+ class MaskMethod {
+
+ @Test
+ @DisplayName("should return null when input map is null")
+ void shouldReturnNullWhenInputIsNull() {
+ assertNull(SecretMasker.mask(null));
+ }
+
+ @Test
+ @DisplayName("should mask simple sensitive keys at root level")
+ void shouldMaskSimpleSensitiveKeys() {
+ Map input =
+ Map.of(
+ "password", "mySecret",
+ "username", "john");
+
+ Map result = SecretMasker.mask(input);
+
+ assertEquals("***REDACTED***", result.get("password"));
+ assertEquals("john", result.get("username"));
+ }
+
+ @Test
+ @DisplayName("should mask keys case-insensitively and with special characters")
+ void shouldMaskKeysCaseInsensitive() {
+ Map input =
+ Map.of(
+ "Api-Key", "12345",
+ "TOKEN", "abcde",
+ "normal", "keepme");
+
+ Map result = SecretMasker.mask(input);
+
+ assertEquals("***REDACTED***", result.get("Api-Key"));
+ assertEquals("***REDACTED***", result.get("TOKEN"));
+ assertEquals("keepme", result.get("normal"));
+ }
+
+ @Test
+ @DisplayName("should mask nested map sensitive keys")
+ void shouldMaskNestedMapSensitiveKeys() {
+ Map input =
+ Map.of(
+ "outer",
+ Map.of(
+ "jwt",
+ "tokenValue",
+ "inner",
+ Map.of(
+ "secret", "deepValue",
+ "other", "ok")));
+
+ Map result = SecretMasker.mask(input);
+
+ Map outer = (Map) result.get("outer");
+ assertEquals("***REDACTED***", outer.get("jwt"));
+ Map inner = (Map) outer.get("inner");
+ assertEquals("***REDACTED***", inner.get("secret"));
+ assertEquals("ok", inner.get("other"));
+ }
+
+ @Test
+ @DisplayName("should mask sensitive keys inside lists")
+ void shouldMaskSensitiveKeysInsideLists() {
+ Map input =
+ Map.of(
+ "list",
+ List.of(
+ Map.of("token", "abc123"),
+ Map.of("username", "john"),
+ "stringValue"));
+
+ Map result = SecretMasker.mask(input);
+
+ List> list = (List>) result.get("list");
+ Map first = (Map) list.get(0);
+ assertEquals("***REDACTED***", first.get("token"));
+ Map second = (Map) list.get(1);
+ assertEquals("john", second.get("username"));
+ assertEquals("stringValue", list.get(2));
+ }
+
+ @Test
+ @DisplayName("should ignore null values")
+ void shouldIgnoreNullValues() {
+ // IMPORTANT: Map.of(...) does not allow nulls -> use a mutable Map instead
+ Map input = new HashMap<>();
+ input.put("password", null);
+ input.put("normal", null);
+
+ Map result = SecretMasker.mask(input);
+
+ // Null values are completely filtered out
+ assertFalse(result.containsKey("password"));
+ assertFalse(result.containsKey("normal"));
+ assertTrue(result.isEmpty(), "Result map should be empty if all entries were null");
+ }
+
+ @Test
+ @DisplayName("should not mask when key does not match pattern")
+ void shouldNotMaskWhenKeyNotSensitive() {
+ Map input = Map.of("email", "test@example.com");
+
+ Map result = SecretMasker.mask(input);
+ assertEquals("test@example.com", result.get("email"));
+ }
+ }
+
+ @Nested
+ @DisplayName("Deep masking edge branches")
+ class DeepMaskBranches {
+
+ @Test
+ @DisplayName("should filter out null values inside nested map")
+ void shouldFilterOutNullValuesInsideNestedMap() {
+ // outer -> { inner -> { "token": null, "username": "john" } }
+ Map inner = new HashMap<>();
+ inner.put("token", null); // <- should be filtered out in the result (branch false)
+ inner.put("username", "john"); // <- should remain
+
+ Map input = Map.of("outer", Map.of("inner", inner));
+
+ Map result = SecretMasker.mask(input);
+
+ Map outer = (Map) result.get("outer");
+ Map maskedInner = (Map) outer.get("inner");
+
+ // "token" was null -> should be completely absent (filter branch in deepMask(Map))
+ assertFalse(maskedInner.containsKey("token"));
+ // "username" remains unchanged
+ assertEquals("john", maskedInner.get("username"));
+ }
+
+ @Test
+ @DisplayName("should not mask when key is null (falls back to deepMask(value))")
+ void shouldNotMaskWhenKeyIsNull() {
+ // Map with null key: { null: "plainText", "password": "toHide" }
+ Map sensitive = new HashMap<>();
+ sensitive.put(null, "plainText"); // <- key == null -> no masking, value stays
+ sensitive.put("password", "toHide"); // <- sensitive key -> will be masked
+
+ Map input = Map.of("outer", sensitive);
+
+ Map result = SecretMasker.mask(input);
+
+ Map outer = (Map) result.get("outer");
+ assertTrue(outer.containsKey(null), "Null key should be preserved");
+ assertEquals("plainText", outer.get(null), "Value for null key must not be masked");
+ assertEquals("***REDACTED***", outer.get("password"), "Sensitive keys must be masked");
+ }
+ }
+}