fix(test): split AgentCommandEnforcementIT into two discoverable top-level IT classes
Failsafe discovers classes by name pattern (*IT, *ITCase, IT*). The nested static classes EnforcementDisabled/EnforcementEnabled inside the outer AgentCommandEnforcementIT were invisible to the test runner — the outer class matched *IT but had no @Test methods; the inner classes did not match the naming convention and were silently skipped. Split into AgentCommandEnforcementDisabledIT and AgentCommandEnforcementEnabledIT — identical logic, each a proper top-level class extending AbstractPostgresIT. All 6 enforcement tests (3 signing-failure + 1 disabled + 2 enabled) now execute and pass. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
package io.cameleer.server.app.controller;
|
||||
|
||||
import io.cameleer.server.app.AbstractPostgresIT;
|
||||
import io.cameleer.server.app.TestSecurityHelper;
|
||||
import io.cameleer.server.core.agent.AgentRegistryService;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.web.client.TestRestTemplate;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* E4/E5 integration test — enforce-signed-commands=false (default).
|
||||
* Non-capable agents must receive a 202 and the command must be enqueued.
|
||||
*/
|
||||
@TestPropertySource(properties = "cameleer.server.security.enforce-signed-commands=false")
|
||||
class AgentCommandEnforcementDisabledIT extends AbstractPostgresIT {
|
||||
|
||||
@Autowired private TestRestTemplate restTemplate;
|
||||
@Autowired private ObjectMapper objectMapper;
|
||||
@Autowired private TestSecurityHelper securityHelper;
|
||||
@Autowired private AgentRegistryService registryService;
|
||||
|
||||
private String operatorJwt;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
securityHelper.installSyntheticUnsignedLicense(Map.of("max_agents", 100));
|
||||
operatorJwt = securityHelper.operatorToken();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
securityHelper.clearTestLicense();
|
||||
}
|
||||
|
||||
@Test
|
||||
void sendCommand_nonCapableAgent_returns202WhenEnforcementDisabled() throws Exception {
|
||||
String agentId = "enforce-off-" + UUID.randomUUID().toString().substring(0, 8);
|
||||
// requireSignedCommands=false (the default overload)
|
||||
registryService.register(agentId, agentId, "test-app", "default", "1.0", List.of(), Map.of());
|
||||
|
||||
ResponseEntity<String> response = sendConfigUpdate(agentId);
|
||||
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.ACCEPTED);
|
||||
JsonNode body = objectMapper.readTree(response.getBody());
|
||||
assertThat(body.has("commandId")).isTrue();
|
||||
}
|
||||
|
||||
private ResponseEntity<String> sendConfigUpdate(String agentId) {
|
||||
return restTemplate.postForEntity(
|
||||
"/api/v1/agents/" + agentId + "/commands",
|
||||
new HttpEntity<>("""
|
||||
{"type": "config-update", "payload": {"key": "value"}}
|
||||
""", securityHelper.authHeaders(operatorJwt)),
|
||||
String.class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package io.cameleer.server.app.controller;
|
||||
|
||||
import io.cameleer.server.app.AbstractPostgresIT;
|
||||
import io.cameleer.server.app.TestSecurityHelper;
|
||||
import io.cameleer.server.core.agent.AgentRegistryService;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.web.client.TestRestTemplate;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* E4/E5 integration test — enforce-signed-commands=true (hard gate).
|
||||
* Non-capable agents must receive 409; capable agents must still receive 202.
|
||||
*/
|
||||
@TestPropertySource(properties = "cameleer.server.security.enforce-signed-commands=true")
|
||||
class AgentCommandEnforcementEnabledIT extends AbstractPostgresIT {
|
||||
|
||||
@Autowired private TestRestTemplate restTemplate;
|
||||
@Autowired private ObjectMapper objectMapper;
|
||||
@Autowired private TestSecurityHelper securityHelper;
|
||||
@Autowired private AgentRegistryService registryService;
|
||||
|
||||
private String operatorJwt;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
securityHelper.installSyntheticUnsignedLicense(Map.of("max_agents", 100));
|
||||
operatorJwt = securityHelper.operatorToken();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
securityHelper.clearTestLicense();
|
||||
}
|
||||
|
||||
@Test
|
||||
void sendCommand_nonCapableAgent_returns409WhenEnforcementEnabled() {
|
||||
String agentId = "enforce-on-noncap-" + UUID.randomUUID().toString().substring(0, 8);
|
||||
registryService.register(agentId, agentId, "test-app", "default", "1.0", List.of(), Map.of());
|
||||
|
||||
ResponseEntity<String> response = sendConfigUpdate(agentId);
|
||||
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.CONFLICT);
|
||||
}
|
||||
|
||||
@Test
|
||||
void sendCommand_capableAgent_returns202WhenEnforcementEnabled() throws Exception {
|
||||
String agentId = "enforce-on-cap-" + UUID.randomUUID().toString().substring(0, 8);
|
||||
// requireSignedCommands=true — agent has been upgraded to verify signatures
|
||||
registryService.register(agentId, agentId, "test-app", "default", "2.0", List.of(), Map.of(), true);
|
||||
|
||||
ResponseEntity<String> response = sendConfigUpdate(agentId);
|
||||
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.ACCEPTED);
|
||||
JsonNode body = objectMapper.readTree(response.getBody());
|
||||
assertThat(body.has("commandId")).isTrue();
|
||||
}
|
||||
|
||||
private ResponseEntity<String> sendConfigUpdate(String agentId) {
|
||||
return restTemplate.postForEntity(
|
||||
"/api/v1/agents/" + agentId + "/commands",
|
||||
new HttpEntity<>("""
|
||||
{"type": "config-update", "payload": {"key": "value"}}
|
||||
""", securityHelper.authHeaders(operatorJwt)),
|
||||
String.class);
|
||||
}
|
||||
}
|
||||
@@ -1,137 +0,0 @@
|
||||
package io.cameleer.server.app.controller;
|
||||
|
||||
import io.cameleer.server.app.AbstractPostgresIT;
|
||||
import io.cameleer.server.app.TestSecurityHelper;
|
||||
import io.cameleer.server.core.agent.AgentRegistryService;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.web.client.TestRestTemplate;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Integration tests for E4 (operator warning) and E5 (hard enforcement) of the SSE signing handoff.
|
||||
* <p>
|
||||
* Two top-level classes share this file. Each spins up its own application context via
|
||||
* {@code @TestPropertySource} to flip the {@code enforce-signed-commands} flag:
|
||||
* <ul>
|
||||
* <li>{@link EnforcementDisabled} — flag off (default): non-capable agent gets 202.</li>
|
||||
* <li>{@link EnforcementEnabled} — flag on: non-capable gets 409, capable gets 202.</li>
|
||||
* </ul>
|
||||
*/
|
||||
class AgentCommandEnforcementIT {
|
||||
|
||||
// ── Flag OFF ─────────────────────────────────────────────────────────────
|
||||
|
||||
@TestPropertySource(properties = "cameleer.server.security.enforce-signed-commands=false")
|
||||
static class EnforcementDisabled extends AbstractPostgresIT {
|
||||
|
||||
@Autowired private TestRestTemplate restTemplate;
|
||||
@Autowired private ObjectMapper objectMapper;
|
||||
@Autowired private TestSecurityHelper securityHelper;
|
||||
@Autowired private AgentRegistryService registryService;
|
||||
|
||||
private String operatorJwt;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
securityHelper.installSyntheticUnsignedLicense(Map.of("max_agents", 100));
|
||||
operatorJwt = securityHelper.operatorToken();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
securityHelper.clearTestLicense();
|
||||
}
|
||||
|
||||
@Test
|
||||
void sendCommand_nonCapableAgent_returns202WhenEnforcementDisabled() throws Exception {
|
||||
String agentId = "enforce-off-" + UUID.randomUUID().toString().substring(0, 8);
|
||||
// requireSignedCommands=false (the default overload)
|
||||
registryService.register(agentId, agentId, "test-app", "default", "1.0", List.of(), Map.of());
|
||||
|
||||
ResponseEntity<String> response = sendConfigUpdate(agentId);
|
||||
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.ACCEPTED);
|
||||
JsonNode body = objectMapper.readTree(response.getBody());
|
||||
assertThat(body.has("commandId")).isTrue();
|
||||
}
|
||||
|
||||
private ResponseEntity<String> sendConfigUpdate(String agentId) {
|
||||
return restTemplate.postForEntity(
|
||||
"/api/v1/agents/" + agentId + "/commands",
|
||||
new HttpEntity<>("""
|
||||
{"type": "config-update", "payload": {"key": "value"}}
|
||||
""", securityHelper.authHeaders(operatorJwt)),
|
||||
String.class);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Flag ON ──────────────────────────────────────────────────────────────
|
||||
|
||||
@TestPropertySource(properties = "cameleer.server.security.enforce-signed-commands=true")
|
||||
static class EnforcementEnabled extends AbstractPostgresIT {
|
||||
|
||||
@Autowired private TestRestTemplate restTemplate;
|
||||
@Autowired private ObjectMapper objectMapper;
|
||||
@Autowired private TestSecurityHelper securityHelper;
|
||||
@Autowired private AgentRegistryService registryService;
|
||||
|
||||
private String operatorJwt;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
securityHelper.installSyntheticUnsignedLicense(Map.of("max_agents", 100));
|
||||
operatorJwt = securityHelper.operatorToken();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
securityHelper.clearTestLicense();
|
||||
}
|
||||
|
||||
@Test
|
||||
void sendCommand_nonCapableAgent_returns409WhenEnforcementEnabled() {
|
||||
String agentId = "enforce-on-noncap-" + UUID.randomUUID().toString().substring(0, 8);
|
||||
registryService.register(agentId, agentId, "test-app", "default", "1.0", List.of(), Map.of());
|
||||
|
||||
ResponseEntity<String> response = sendConfigUpdate(agentId);
|
||||
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.CONFLICT);
|
||||
}
|
||||
|
||||
@Test
|
||||
void sendCommand_capableAgent_returns202WhenEnforcementEnabled() throws Exception {
|
||||
String agentId = "enforce-on-cap-" + UUID.randomUUID().toString().substring(0, 8);
|
||||
// requireSignedCommands=true — agent has been upgraded to verify signatures
|
||||
registryService.register(agentId, agentId, "test-app", "default", "2.0", List.of(), Map.of(), true);
|
||||
|
||||
ResponseEntity<String> response = sendConfigUpdate(agentId);
|
||||
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.ACCEPTED);
|
||||
JsonNode body = objectMapper.readTree(response.getBody());
|
||||
assertThat(body.has("commandId")).isTrue();
|
||||
}
|
||||
|
||||
private ResponseEntity<String> sendConfigUpdate(String agentId) {
|
||||
return restTemplate.postForEntity(
|
||||
"/api/v1/agents/" + agentId + "/commands",
|
||||
new HttpEntity<>("""
|
||||
{"type": "config-update", "payload": {"key": "value"}}
|
||||
""", securityHelper.authHeaders(operatorJwt)),
|
||||
String.class);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user