AGENTS.md + code cleanup
This commit is contained in:
parent
21b4169130
commit
9df61e0cd3
20 changed files with 365 additions and 910 deletions
|
|
@ -5,13 +5,9 @@ import org.junit.jupiter.api.*;
|
|||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.security.*;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
|
|
@ -32,17 +28,12 @@ class SecureCompletionClientE2ETest {
|
|||
assertTrue(keyDir.mkdirs(), "Key directory should be created");
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
// Cleanup is handled by @TempDir
|
||||
}
|
||||
|
||||
// ── Full Lifecycle E2E Tests ──────────────────────────────────────
|
||||
|
||||
@Test
|
||||
@Order(1)
|
||||
@DisplayName("E2E: Generate keys, save to disk, load in new client, validate")
|
||||
void e2e_fullLifecycle_generateSaveLoadValidate() throws Exception {
|
||||
void e2e_fullLifecycle_generateSaveLoadValidate() {
|
||||
// Step 1: Generate keys and save to disk
|
||||
SecureCompletionClient generateClient = new SecureCompletionClient(BASE_URL, false, true, 2);
|
||||
generateClient.generateKeys(true, keyDir.getAbsolutePath(), TEST_PASSWORD);
|
||||
|
|
@ -83,7 +74,7 @@ class SecureCompletionClientE2ETest {
|
|||
@Test
|
||||
@Order(2)
|
||||
@DisplayName("E2E: Generate plaintext keys, load, and validate")
|
||||
void e2e_plaintextKeys_generateLoadValidate() throws Exception {
|
||||
void e2e_plaintextKeys_generateLoadValidate() {
|
||||
// Generate plaintext keys (no password)
|
||||
SecureCompletionClient client = new SecureCompletionClient();
|
||||
client.generateKeys(true, keyDir.getAbsolutePath(), null);
|
||||
|
|
@ -137,7 +128,7 @@ class SecureCompletionClientE2ETest {
|
|||
@Test
|
||||
@Order(4)
|
||||
@DisplayName("E2E: HTTP status mapping covers all documented cases")
|
||||
void e2e_httpStatusMapping_allCases() throws Exception {
|
||||
void e2e_httpStatusMapping_allCases() {
|
||||
SecureCompletionClient client = new SecureCompletionClient();
|
||||
|
||||
// 200 - success (null exception)
|
||||
|
|
@ -187,7 +178,7 @@ class SecureCompletionClientE2ETest {
|
|||
@Test
|
||||
@Order(5)
|
||||
@DisplayName("E2E: Retryable status codes match Constants.RETRYABLE_STATUS_CODES")
|
||||
void e2e_retryableStatusCodes_matchConstants() throws Exception {
|
||||
void e2e_retryableStatusCodes_matchConstants() {
|
||||
SecureCompletionClient client = new SecureCompletionClient();
|
||||
|
||||
for (int code : Constants.RETRYABLE_STATUS_CODES) {
|
||||
|
|
@ -200,13 +191,16 @@ class SecureCompletionClientE2ETest {
|
|||
@Test
|
||||
@Order(6)
|
||||
@DisplayName("E2E: URL encoding of public key PEM")
|
||||
void e2e_urlEncoding_publicKey() throws Exception {
|
||||
void e2e_urlEncoding_publicKey() {
|
||||
SecureCompletionClient client = new SecureCompletionClient();
|
||||
|
||||
String pemKey = "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB" +
|
||||
"IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBI" +
|
||||
"kAMBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB" +
|
||||
"IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBIjAN\n-----END PUBLIC KEY-----";
|
||||
String pemKey = """
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB\
|
||||
IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBI\
|
||||
kAMBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB\
|
||||
IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBIjAN
|
||||
-----END PUBLIC KEY-----""";
|
||||
|
||||
String encoded = client.urlEncodePublicKey(pemKey);
|
||||
|
||||
|
|
@ -266,7 +260,7 @@ class SecureCompletionClientE2ETest {
|
|||
@Test
|
||||
@Order(8)
|
||||
@DisplayName("E2E: Client constructor parameters are correctly set")
|
||||
void e2e_clientConstructor_parametersSetCorrectly() throws Exception {
|
||||
void e2e_clientConstructor_parametersSetCorrectly() {
|
||||
SecureCompletionClient client = new SecureCompletionClient(
|
||||
"https://custom.api.com",
|
||||
true,
|
||||
|
|
@ -283,7 +277,7 @@ class SecureCompletionClientE2ETest {
|
|||
@Test
|
||||
@Order(9)
|
||||
@DisplayName("E2E: Client strips trailing slashes from routerUrl")
|
||||
void e2e_clientConstructor_stripsTrailingSlashes() throws Exception {
|
||||
void e2e_clientConstructor_stripsTrailingSlashes() {
|
||||
SecureCompletionClient client = new SecureCompletionClient(
|
||||
"https://api.example.com///",
|
||||
false, true, 1
|
||||
|
|
@ -295,7 +289,7 @@ class SecureCompletionClientE2ETest {
|
|||
@Test
|
||||
@Order(10)
|
||||
@DisplayName("E2E: Client uses default values when constructed with no args")
|
||||
void e2e_clientConstructor_defaultValues() throws Exception {
|
||||
void e2e_clientConstructor_defaultValues() {
|
||||
SecureCompletionClient client = new SecureCompletionClient();
|
||||
|
||||
assertEquals(Constants.DEFAULT_BASE_URL, client.getRouterUrl());
|
||||
|
|
@ -329,7 +323,7 @@ class SecureCompletionClientE2ETest {
|
|||
@Test
|
||||
@Order(12)
|
||||
@DisplayName("E2E: Generate keys without saving produces in-memory keys")
|
||||
void e2e_generateKeys_noSave_producesInMemoryKeys() throws Exception {
|
||||
void e2e_generateKeys_noSave_producesInMemoryKeys() {
|
||||
SecureCompletionClient client = new SecureCompletionClient();
|
||||
client.generateKeys(false);
|
||||
|
||||
|
|
@ -338,13 +332,13 @@ class SecureCompletionClientE2ETest {
|
|||
|
||||
assertNotNull(privateKey, "Private key should be in memory");
|
||||
assertNotNull(publicPem, "Public PEM should be in memory");
|
||||
assertTrue(privateKey.getAlgorithm().equals("RSA"));
|
||||
assertEquals("RSA", privateKey.getAlgorithm());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(13)
|
||||
@DisplayName("E2E: SecurityError is thrown for null key validation")
|
||||
void e2e_nullKeyValidation_throwsSecurityError() throws Exception {
|
||||
void e2e_nullKeyValidation_throwsSecurityError() {
|
||||
SecureCompletionClient client = new SecureCompletionClient();
|
||||
|
||||
SecurityError error = assertThrows(SecurityError.class,
|
||||
|
|
@ -357,7 +351,7 @@ class SecureCompletionClientE2ETest {
|
|||
@Test
|
||||
@Order(14)
|
||||
@DisplayName("E2E: mapHttpStatus returns null for 200 status")
|
||||
void e2e_mapHttpStatus_200_returnsNull() throws Exception {
|
||||
void e2e_mapHttpStatus_200_returnsNull() {
|
||||
SecureCompletionClient client = new SecureCompletionClient();
|
||||
Exception result = client.mapHttpStatus(200, "Success");
|
||||
|
||||
|
|
@ -367,7 +361,7 @@ class SecureCompletionClientE2ETest {
|
|||
@Test
|
||||
@Order(15)
|
||||
@DisplayName("E2E: mapHttpStatus includes response body in error message")
|
||||
void e2e_mapHttpStatus_includesResponseBody() throws Exception {
|
||||
void e2e_mapHttpStatus_includesResponseBody() {
|
||||
SecureCompletionClient client = new SecureCompletionClient();
|
||||
|
||||
Exception e400 = client.mapHttpStatus(400, "Invalid parameter: email");
|
||||
|
|
|
|||
|
|
@ -5,12 +5,7 @@ import ai.nomyo.util.Pass2Key;
|
|||
import org.junit.jupiter.api.*;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
|
@ -51,7 +46,7 @@ class SecureCompletionClientTest {
|
|||
|
||||
assertNotNull(privateKey, "Private key should not be null");
|
||||
assertNotNull(publicPemKey, "Public PEM key should not be null");
|
||||
assertTrue(privateKey.getAlgorithm().equals("RSA"), "Key algorithm should be RSA");
|
||||
assertEquals("RSA", privateKey.getAlgorithm(), "Key algorithm should be RSA");
|
||||
assertTrue(publicPemKey.contains("BEGIN PUBLIC KEY"), "Public key should be valid PEM");
|
||||
}
|
||||
|
||||
|
|
@ -81,8 +76,8 @@ class SecureCompletionClientTest {
|
|||
client2.generateKeys(false);
|
||||
PrivateKey secondKey = client2.getPrivateKey();
|
||||
|
||||
assertNotEquals(firstKey.getEncoded().length, secondKey.getEncoded().length,
|
||||
"Different keys should have different encoded lengths");
|
||||
assertNotEquals(java.util.Arrays.hashCode(firstKey.getEncoded()), java.util.Arrays.hashCode(secondKey.getEncoded()),
|
||||
"Different keys should have different encoded content");
|
||||
}
|
||||
|
||||
// ── Key Generation with File Save Tests ───────────────────────────
|
||||
|
|
@ -90,7 +85,7 @@ class SecureCompletionClientTest {
|
|||
@Test
|
||||
@Order(4)
|
||||
@DisplayName("generateKeys with saveToFile=true should create key files")
|
||||
void generateKeys_withSaveToFile_shouldCreateKeyFiles(@TempDir Path tempDir) throws Exception {
|
||||
void generateKeys_withSaveToFile_shouldCreateKeyFiles(@TempDir Path tempDir) {
|
||||
File keyDir = tempDir.toFile();
|
||||
|
||||
client.generateKeys(true, keyDir.getAbsolutePath(), null);
|
||||
|
|
@ -124,7 +119,7 @@ class SecureCompletionClientTest {
|
|||
@Test
|
||||
@Order(6)
|
||||
@DisplayName("generateKeys should not overwrite existing key files")
|
||||
void generateKeys_shouldNotOverwriteExistingKeys(@TempDir Path tempDir) throws Exception {
|
||||
void generateKeys_shouldNotOverwriteExistingKeys(@TempDir Path tempDir) {
|
||||
File keyDir = tempDir.toFile();
|
||||
|
||||
client.generateKeys(true, keyDir.getAbsolutePath(), null);
|
||||
|
|
@ -143,7 +138,7 @@ class SecureCompletionClientTest {
|
|||
@Test
|
||||
@Order(7)
|
||||
@DisplayName("loadKeys should load plaintext private key from file")
|
||||
void loadKeys_plaintext_shouldLoadPrivateKey(@TempDir Path tempDir) throws Exception {
|
||||
void loadKeys_plaintext_shouldLoadPrivateKey(@TempDir Path tempDir) {
|
||||
File keyDir = tempDir.toFile();
|
||||
client.generateKeys(true, keyDir.getAbsolutePath(), null);
|
||||
|
||||
|
|
@ -164,7 +159,7 @@ class SecureCompletionClientTest {
|
|||
@Test
|
||||
@Order(8)
|
||||
@DisplayName("loadKeys should load encrypted private key with correct password")
|
||||
void loadKeys_encrypted_correctPassword_shouldLoadPrivateKey(@TempDir Path tempDir) throws Exception {
|
||||
void loadKeys_encrypted_correctPassword_shouldLoadPrivateKey(@TempDir Path tempDir) {
|
||||
File keyDir = tempDir.toFile();
|
||||
client.generateKeys(true, keyDir.getAbsolutePath(), TEST_PASSWORD);
|
||||
|
||||
|
|
@ -186,7 +181,7 @@ class SecureCompletionClientTest {
|
|||
@Test
|
||||
@Order(9)
|
||||
@DisplayName("loadKeys should handle wrong password gracefully")
|
||||
void loadKeys_encrypted_wrongPassword_shouldHandleGracefully(@TempDir Path tempDir) throws Exception {
|
||||
void loadKeys_encrypted_wrongPassword_shouldHandleGracefully(@TempDir Path tempDir) {
|
||||
File keyDir = tempDir.toFile();
|
||||
client.generateKeys(true, keyDir.getAbsolutePath(), TEST_PASSWORD);
|
||||
|
||||
|
|
@ -220,7 +215,7 @@ class SecureCompletionClientTest {
|
|||
@Test
|
||||
@Order(11)
|
||||
@DisplayName("validateRsaKey should accept valid 4096-bit key")
|
||||
void validateRsaKey_validKey_shouldPass() throws Exception {
|
||||
void validateRsaKey_validKey_shouldPass() {
|
||||
client.generateKeys(false);
|
||||
PrivateKey key = client.getPrivateKey();
|
||||
|
||||
|
|
@ -231,7 +226,7 @@ class SecureCompletionClientTest {
|
|||
@Test
|
||||
@Order(12)
|
||||
@DisplayName("validateRsaKey should reject null key")
|
||||
void validateRsaKey_nullKey_shouldThrowSecurityError() throws Exception {
|
||||
void validateRsaKey_nullKey_shouldThrowSecurityError() {
|
||||
SecurityError error = assertThrows(SecurityError.class, () ->
|
||||
client.validateRsaKey(null));
|
||||
|
||||
|
|
@ -273,7 +268,7 @@ class SecureCompletionClientTest {
|
|||
@Test
|
||||
@Order(15)
|
||||
@DisplayName("Full roundtrip: generate, save, load should produce same key")
|
||||
void roundtrip_generateSaveLoad_shouldProduceSameKey(@TempDir Path tempDir) throws Exception {
|
||||
void roundtrip_generateSaveLoad_shouldProduceSameKey(@TempDir Path tempDir) {
|
||||
File keyDir = tempDir.toFile();
|
||||
|
||||
// Generate and save
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue