fix: use correct role-based JWT tokens in all integration tests

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-03-16 20:03:38 +01:00
parent 39f9925e71
commit 9f74e47ecf
11 changed files with 76 additions and 28 deletions

View File

@@ -27,11 +27,39 @@ public class TestSecurityHelper {
}
/**
* Registers a test agent and returns a valid JWT access token for it.
* Registers a test agent and returns a valid JWT access token with AGENT role.
*/
public String registerTestAgent(String agentId) {
agentRegistryService.register(agentId, "test", "test-group", "1.0", List.of(), Map.of());
return jwtService.createAccessToken(agentId, "test-group");
return jwtService.createAccessToken(agentId, "test-group", List.of("AGENT"));
}
/**
* Returns a valid JWT access token with the given roles (no agent registration).
*/
public String createToken(String subject, String group, List<String> roles) {
return jwtService.createAccessToken(subject, group, roles);
}
/**
* Returns a valid JWT access token with OPERATOR role.
*/
public String operatorToken() {
return jwtService.createAccessToken("test-operator", "user", List.of("OPERATOR"));
}
/**
* Returns a valid JWT access token with ADMIN role.
*/
public String adminToken() {
return jwtService.createAccessToken("test-admin", "user", List.of("ADMIN"));
}
/**
* Returns a valid JWT access token with VIEWER role.
*/
public String viewerToken() {
return jwtService.createAccessToken("test-viewer", "user", List.of("VIEWER"));
}
/**

View File

@@ -29,11 +29,13 @@ class AgentCommandControllerIT extends AbstractPostgresIT {
@Autowired
private TestSecurityHelper securityHelper;
private String jwt;
private String agentJwt;
private String operatorJwt;
@BeforeEach
void setUp() {
jwt = securityHelper.registerTestAgent("test-agent-command-it");
agentJwt = securityHelper.registerTestAgent("test-agent-command-it");
operatorJwt = securityHelper.operatorToken();
}
private ResponseEntity<String> registerAgent(String agentId, String name, String group) {
@@ -65,7 +67,7 @@ class AgentCommandControllerIT extends AbstractPostgresIT {
ResponseEntity<String> response = restTemplate.postForEntity(
"/api/v1/agents/" + agentId + "/commands",
new HttpEntity<>(commandJson, securityHelper.authHeaders(jwt)),
new HttpEntity<>(commandJson, securityHelper.authHeaders(operatorJwt)),
String.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.ACCEPTED);
@@ -88,7 +90,7 @@ class AgentCommandControllerIT extends AbstractPostgresIT {
ResponseEntity<String> response = restTemplate.postForEntity(
"/api/v1/agents/groups/" + group + "/commands",
new HttpEntity<>(commandJson, securityHelper.authHeaders(jwt)),
new HttpEntity<>(commandJson, securityHelper.authHeaders(operatorJwt)),
String.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.ACCEPTED);
@@ -110,7 +112,7 @@ class AgentCommandControllerIT extends AbstractPostgresIT {
ResponseEntity<String> response = restTemplate.postForEntity(
"/api/v1/agents/commands",
new HttpEntity<>(commandJson, securityHelper.authHeaders(jwt)),
new HttpEntity<>(commandJson, securityHelper.authHeaders(operatorJwt)),
String.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.ACCEPTED);
@@ -131,7 +133,7 @@ class AgentCommandControllerIT extends AbstractPostgresIT {
ResponseEntity<String> cmdResponse = restTemplate.postForEntity(
"/api/v1/agents/" + agentId + "/commands",
new HttpEntity<>(commandJson, securityHelper.authHeaders(jwt)),
new HttpEntity<>(commandJson, securityHelper.authHeaders(operatorJwt)),
String.class);
JsonNode cmdBody = objectMapper.readTree(cmdResponse.getBody());
@@ -140,7 +142,7 @@ class AgentCommandControllerIT extends AbstractPostgresIT {
ResponseEntity<Void> ackResponse = restTemplate.exchange(
"/api/v1/agents/" + agentId + "/commands/" + commandId + "/ack",
HttpMethod.POST,
new HttpEntity<>(securityHelper.authHeadersNoBody(jwt)),
new HttpEntity<>(securityHelper.authHeadersNoBody(agentJwt)),
Void.class);
assertThat(ackResponse.getStatusCode()).isEqualTo(HttpStatus.OK);
@@ -154,7 +156,7 @@ class AgentCommandControllerIT extends AbstractPostgresIT {
ResponseEntity<Void> response = restTemplate.exchange(
"/api/v1/agents/" + agentId + "/commands/nonexistent-cmd-id/ack",
HttpMethod.POST,
new HttpEntity<>(securityHelper.authHeadersNoBody(jwt)),
new HttpEntity<>(securityHelper.authHeadersNoBody(agentJwt)),
Void.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
@@ -168,7 +170,7 @@ class AgentCommandControllerIT extends AbstractPostgresIT {
ResponseEntity<String> response = restTemplate.postForEntity(
"/api/v1/agents/nonexistent-agent-xyz/commands",
new HttpEntity<>(commandJson, securityHelper.authHeaders(jwt)),
new HttpEntity<>(commandJson, securityHelper.authHeaders(operatorJwt)),
String.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);

View File

@@ -28,10 +28,12 @@ class AgentRegistrationControllerIT extends AbstractPostgresIT {
private TestSecurityHelper securityHelper;
private String jwt;
private String viewerJwt;
@BeforeEach
void setUp() {
jwt = securityHelper.registerTestAgent("test-agent-registration-it");
viewerJwt = securityHelper.viewerToken();
}
private ResponseEntity<String> registerAgent(String agentId, String name) {
@@ -114,7 +116,7 @@ class AgentRegistrationControllerIT extends AbstractPostgresIT {
ResponseEntity<String> response = restTemplate.exchange(
"/api/v1/agents",
HttpMethod.GET,
new HttpEntity<>(securityHelper.authHeadersNoBody(jwt)),
new HttpEntity<>(securityHelper.authHeadersNoBody(viewerJwt)),
String.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
@@ -131,7 +133,7 @@ class AgentRegistrationControllerIT extends AbstractPostgresIT {
ResponseEntity<String> response = restTemplate.exchange(
"/api/v1/agents?status=LIVE",
HttpMethod.GET,
new HttpEntity<>(securityHelper.authHeadersNoBody(jwt)),
new HttpEntity<>(securityHelper.authHeadersNoBody(viewerJwt)),
String.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
@@ -148,7 +150,7 @@ class AgentRegistrationControllerIT extends AbstractPostgresIT {
ResponseEntity<String> response = restTemplate.exchange(
"/api/v1/agents?status=INVALID",
HttpMethod.GET,
new HttpEntity<>(securityHelper.authHeadersNoBody(jwt)),
new HttpEntity<>(securityHelper.authHeadersNoBody(viewerJwt)),
String.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST);

View File

@@ -45,10 +45,12 @@ class AgentSseControllerIT extends AbstractPostgresIT {
private int port;
private String jwt;
private String operatorJwt;
@BeforeEach
void setUp() {
jwt = securityHelper.registerTestAgent("test-agent-sse-it");
operatorJwt = securityHelper.operatorToken();
}
private ResponseEntity<String> registerAgent(String agentId, String name, String group) {
@@ -76,7 +78,7 @@ class AgentSseControllerIT extends AbstractPostgresIT {
return restTemplate.postForEntity(
"/api/v1/agents/" + agentId + "/commands",
new HttpEntity<>(json, securityHelper.authHeaders(jwt)),
new HttpEntity<>(json, securityHelper.authHeaders(operatorJwt)),
String.class);
}

View File

@@ -34,6 +34,7 @@ class DetailControllerIT extends AbstractPostgresIT {
private final ObjectMapper objectMapper = new ObjectMapper();
private String jwt;
private String viewerJwt;
private String seededExecutionId;
/**
@@ -43,6 +44,7 @@ class DetailControllerIT extends AbstractPostgresIT {
@BeforeAll
void seedTestData() {
jwt = securityHelper.registerTestAgent("test-agent-detail-it");
viewerJwt = securityHelper.viewerToken();
String json = """
{
@@ -217,7 +219,7 @@ class DetailControllerIT extends AbstractPostgresIT {
}
private ResponseEntity<String> detailGet(String path) {
HttpHeaders headers = securityHelper.authHeadersNoBody(jwt);
HttpHeaders headers = securityHelper.authHeadersNoBody(viewerJwt);
return restTemplate.exchange(
"/api/v1/executions" + path,
HttpMethod.GET,

View File

@@ -29,6 +29,7 @@ class DiagramRenderControllerIT extends AbstractPostgresIT {
private TestSecurityHelper securityHelper;
private String jwt;
private String viewerJwt;
private String contentHash;
/**
@@ -37,6 +38,7 @@ class DiagramRenderControllerIT extends AbstractPostgresIT {
@BeforeEach
void seedDiagram() {
jwt = securityHelper.registerTestAgent("test-agent-diagram-render-it");
viewerJwt = securityHelper.viewerToken();
String json = """
{
@@ -73,7 +75,7 @@ class DiagramRenderControllerIT extends AbstractPostgresIT {
@Test
void getSvg_withAcceptHeader_returnsSvg() {
HttpHeaders headers = securityHelper.authHeadersNoBody(jwt);
HttpHeaders headers = securityHelper.authHeadersNoBody(viewerJwt);
headers.set("Accept", "image/svg+xml");
ResponseEntity<String> response = restTemplate.exchange(
@@ -90,7 +92,7 @@ class DiagramRenderControllerIT extends AbstractPostgresIT {
@Test
void getJson_withAcceptHeader_returnsJson() {
HttpHeaders headers = securityHelper.authHeadersNoBody(jwt);
HttpHeaders headers = securityHelper.authHeadersNoBody(viewerJwt);
headers.set("Accept", "application/json");
ResponseEntity<String> response = restTemplate.exchange(
@@ -107,7 +109,7 @@ class DiagramRenderControllerIT extends AbstractPostgresIT {
@Test
void getNonExistentHash_returns404() {
HttpHeaders headers = securityHelper.authHeadersNoBody(jwt);
HttpHeaders headers = securityHelper.authHeadersNoBody(viewerJwt);
headers.set("Accept", "image/svg+xml");
ResponseEntity<String> response = restTemplate.exchange(
@@ -122,7 +124,7 @@ class DiagramRenderControllerIT extends AbstractPostgresIT {
@Test
void getWithNoAcceptHeader_defaultsToSvg() {
HttpHeaders headers = securityHelper.authHeadersNoBody(jwt);
HttpHeaders headers = securityHelper.authHeadersNoBody(viewerJwt);
ResponseEntity<String> response = restTemplate.exchange(
"/api/v1/diagrams/{hash}/render",

View File

@@ -35,6 +35,7 @@ class SearchControllerIT extends AbstractPostgresIT {
private final ObjectMapper objectMapper = new ObjectMapper();
private String jwt;
private String viewerJwt;
/**
* Seed test data: Insert executions with varying statuses, times, durations,
@@ -43,6 +44,7 @@ class SearchControllerIT extends AbstractPostgresIT {
@BeforeAll
void seedTestData() {
jwt = securityHelper.registerTestAgent("test-agent-search-it");
viewerJwt = securityHelper.viewerToken();
// Execution 1: COMPLETED, short duration, no errors
ingest("""
@@ -376,7 +378,7 @@ class SearchControllerIT extends AbstractPostgresIT {
return restTemplate.exchange(
"/api/v1/search/executions",
HttpMethod.POST,
new HttpEntity<>(jsonBody, securityHelper.authHeaders(jwt)),
new HttpEntity<>(jsonBody, securityHelper.authHeaders(viewerJwt)),
String.class);
}
}

View File

@@ -153,13 +153,13 @@ class JwtRefreshIT extends AbstractPostgresIT {
JsonNode refreshBody2 = objectMapper.readTree(refreshResponse.getBody());
String newAccessToken = refreshBody2.get("accessToken").asText();
// Use the new access token to hit a protected endpoint
// Use the new access token to hit a protected endpoint accessible by AGENT role
HttpHeaders authHeaders = new HttpHeaders();
authHeaders.set("Authorization", "Bearer " + newAccessToken);
authHeaders.set("X-Cameleer-Protocol-Version", "1");
ResponseEntity<String> response = restTemplate.exchange(
"/api/v1/agents",
"/api/v1/search/executions",
HttpMethod.GET,
new HttpEntity<>(authHeaders),
String.class);

View File

@@ -81,13 +81,13 @@ class RegistrationSecurityIT extends AbstractPostgresIT {
JsonNode regBody = objectMapper.readTree(regResponse.getBody());
String accessToken = regBody.get("accessToken").asText();
// Use the access token to hit a protected endpoint
// Use the access token to hit a protected endpoint accessible by AGENT role
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", "Bearer " + accessToken);
headers.set("X-Cameleer-Protocol-Version", "1");
ResponseEntity<String> response = restTemplate.exchange(
"/api/v1/agents",
"/api/v1/search/executions",
HttpMethod.GET,
new HttpEntity<>(headers),
String.class);

View File

@@ -28,10 +28,12 @@ class SecurityFilterIT extends AbstractPostgresIT {
private TestSecurityHelper securityHelper;
private String jwt;
private String viewerJwt;
@BeforeEach
void setUp() {
jwt = securityHelper.registerTestAgent("test-agent-security-filter-it");
viewerJwt = securityHelper.viewerToken();
}
@Test
@@ -53,7 +55,7 @@ class SecurityFilterIT extends AbstractPostgresIT {
ResponseEntity<String> response = restTemplate.exchange(
"/api/v1/agents",
HttpMethod.GET,
new HttpEntity<>(securityHelper.authHeadersNoBody(jwt)),
new HttpEntity<>(securityHelper.authHeadersNoBody(viewerJwt)),
String.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);

View File

@@ -1,6 +1,7 @@
package com.cameleer3.server.app.security;
import com.cameleer3.server.app.AbstractPostgresIT;
import com.cameleer3.server.app.TestSecurityHelper;
import com.cameleer3.server.core.security.Ed25519SigningService;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
@@ -52,6 +53,9 @@ class SseSigningIT extends AbstractPostgresIT {
@Autowired
private ObjectMapper objectMapper;
@Autowired
private TestSecurityHelper securityHelper;
@Autowired
private Ed25519SigningService ed25519SigningService;
@@ -165,6 +169,7 @@ class SseSigningIT extends AbstractPostgresIT {
String agentId = "sse-sign-it-" + UUID.randomUUID().toString().substring(0, 8);
JsonNode registration = registerAgentWithAuth(agentId);
String accessToken = registration.get("accessToken").asText();
String operatorToken = securityHelper.operatorToken();
String serverPublicKey = registration.get("serverPublicKey").asText();
SseStream stream = openSseStream(agentId, accessToken);
@@ -177,7 +182,7 @@ class SseSigningIT extends AbstractPostgresIT {
await().atMost(10, TimeUnit.SECONDS).pollInterval(200, TimeUnit.MILLISECONDS)
.ignoreExceptions()
.until(() -> {
sendCommand(agentId, "config-update", originalPayload, accessToken);
sendCommand(agentId, "config-update", originalPayload, operatorToken);
List<String> lines = stream.snapshot();
return lines.stream().anyMatch(l -> l.contains("event:config-update"));
});
@@ -221,6 +226,7 @@ class SseSigningIT extends AbstractPostgresIT {
String agentId = "sse-sign-trace-" + UUID.randomUUID().toString().substring(0, 8);
JsonNode registration = registerAgentWithAuth(agentId);
String accessToken = registration.get("accessToken").asText();
String operatorToken = securityHelper.operatorToken();
String serverPublicKey = registration.get("serverPublicKey").asText();
SseStream stream = openSseStream(agentId, accessToken);
@@ -232,7 +238,7 @@ class SseSigningIT extends AbstractPostgresIT {
await().atMost(10, TimeUnit.SECONDS).pollInterval(200, TimeUnit.MILLISECONDS)
.ignoreExceptions()
.until(() -> {
sendCommand(agentId, "deep-trace", originalPayload, accessToken);
sendCommand(agentId, "deep-trace", originalPayload, operatorToken);
List<String> lines = stream.snapshot();
return lines.stream().anyMatch(l -> l.contains("event:deep-trace"));
});