feat: add ThresholdAdminController and AuditLogController with integration tests

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-03-17 15:57:23 +01:00
parent c6da858c2f
commit 321b8808cc
4 changed files with 367 additions and 0 deletions

View File

@@ -0,0 +1,112 @@
package com.cameleer3.server.app.controller;
import com.cameleer3.server.app.AbstractPostgresIT;
import com.cameleer3.server.app.TestSecurityHelper;
import com.cameleer3.server.core.admin.AuditCategory;
import com.cameleer3.server.core.admin.AuditResult;
import com.cameleer3.server.core.admin.AuditService;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
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.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
class AuditLogControllerIT extends AbstractPostgresIT {
@Autowired
private TestRestTemplate restTemplate;
@Autowired
private ObjectMapper objectMapper;
@Autowired
private TestSecurityHelper securityHelper;
@Autowired
private AuditService auditService;
private String adminJwt;
private String viewerJwt;
@BeforeEach
void setUp() {
adminJwt = securityHelper.adminToken();
viewerJwt = securityHelper.viewerToken();
}
@Test
void getAuditLog_asAdmin_returns200() throws Exception {
// Insert a test audit entry
auditService.log("test-admin", "test_action", AuditCategory.CONFIG,
"test-target", Map.of("key", "value"), AuditResult.SUCCESS, null);
ResponseEntity<String> response = restTemplate.exchange(
"/api/v1/admin/audit", HttpMethod.GET,
new HttpEntity<>(securityHelper.authHeadersNoBody(adminJwt)),
String.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
JsonNode body = objectMapper.readTree(response.getBody());
assertThat(body.has("items")).isTrue();
assertThat(body.has("totalCount")).isTrue();
assertThat(body.get("totalCount").asLong()).isGreaterThanOrEqualTo(1);
}
@Test
void getAuditLog_asViewer_returns403() {
ResponseEntity<String> response = restTemplate.exchange(
"/api/v1/admin/audit", HttpMethod.GET,
new HttpEntity<>(securityHelper.authHeadersNoBody(viewerJwt)),
String.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN);
}
@Test
void getAuditLog_withCategoryFilter_returnsFilteredResults() throws Exception {
auditService.log("filter-test", "infra_action", AuditCategory.INFRA,
"infra-target", null, AuditResult.SUCCESS, null);
ResponseEntity<String> response = restTemplate.exchange(
"/api/v1/admin/audit?category=INFRA", HttpMethod.GET,
new HttpEntity<>(securityHelper.authHeadersNoBody(adminJwt)),
String.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
JsonNode body = objectMapper.readTree(response.getBody());
assertThat(body.get("items").isArray()).isTrue();
}
@Test
void getAuditLog_withPagination_respectsPageSize() throws Exception {
ResponseEntity<String> response = restTemplate.exchange(
"/api/v1/admin/audit?page=0&size=5", HttpMethod.GET,
new HttpEntity<>(securityHelper.authHeadersNoBody(adminJwt)),
String.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
JsonNode body = objectMapper.readTree(response.getBody());
assertThat(body.get("pageSize").asInt()).isEqualTo(5);
}
@Test
void getAuditLog_maxPageSizeEnforced() throws Exception {
ResponseEntity<String> response = restTemplate.exchange(
"/api/v1/admin/audit?size=500", HttpMethod.GET,
new HttpEntity<>(securityHelper.authHeadersNoBody(adminJwt)),
String.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
JsonNode body = objectMapper.readTree(response.getBody());
assertThat(body.get("pageSize").asInt()).isEqualTo(100);
}
}

View File

@@ -0,0 +1,125 @@
package com.cameleer3.server.app.controller;
import com.cameleer3.server.app.AbstractPostgresIT;
import com.cameleer3.server.app.TestSecurityHelper;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
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.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import static org.assertj.core.api.Assertions.assertThat;
class ThresholdAdminControllerIT extends AbstractPostgresIT {
@Autowired
private TestRestTemplate restTemplate;
@Autowired
private ObjectMapper objectMapper;
@Autowired
private TestSecurityHelper securityHelper;
private String adminJwt;
private String viewerJwt;
@BeforeEach
void setUp() {
adminJwt = securityHelper.adminToken();
viewerJwt = securityHelper.viewerToken();
}
@Test
void getThresholds_asAdmin_returnsDefaults() throws Exception {
ResponseEntity<String> response = restTemplate.exchange(
"/api/v1/admin/thresholds", HttpMethod.GET,
new HttpEntity<>(securityHelper.authHeadersNoBody(adminJwt)),
String.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
JsonNode body = objectMapper.readTree(response.getBody());
assertThat(body.has("database")).isTrue();
assertThat(body.has("opensearch")).isTrue();
assertThat(body.path("database").path("connectionPoolWarning").asInt()).isEqualTo(80);
}
@Test
void getThresholds_asViewer_returns403() {
ResponseEntity<String> response = restTemplate.exchange(
"/api/v1/admin/thresholds", HttpMethod.GET,
new HttpEntity<>(securityHelper.authHeadersNoBody(viewerJwt)),
String.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN);
}
@Test
void updateThresholds_asAdmin_returns200() throws Exception {
String json = """
{
"database": {
"connectionPoolWarning": 70,
"connectionPoolCritical": 90,
"queryDurationWarning": 2.0,
"queryDurationCritical": 15.0
},
"opensearch": {
"clusterHealthWarning": "YELLOW",
"clusterHealthCritical": "RED",
"queueDepthWarning": 200,
"queueDepthCritical": 1000,
"jvmHeapWarning": 80,
"jvmHeapCritical": 95,
"failedDocsWarning": 5,
"failedDocsCritical": 20
}
}
""";
ResponseEntity<String> response = restTemplate.exchange(
"/api/v1/admin/thresholds", HttpMethod.PUT,
new HttpEntity<>(json, securityHelper.authHeaders(adminJwt)),
String.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
JsonNode body = objectMapper.readTree(response.getBody());
assertThat(body.path("database").path("connectionPoolWarning").asInt()).isEqualTo(70);
}
@Test
void updateThresholds_invalidWarningGreaterThanCritical_returns400() {
String json = """
{
"database": {
"connectionPoolWarning": 95,
"connectionPoolCritical": 80,
"queryDurationWarning": 2.0,
"queryDurationCritical": 15.0
},
"opensearch": {
"clusterHealthWarning": "YELLOW",
"clusterHealthCritical": "RED",
"queueDepthWarning": 100,
"queueDepthCritical": 500,
"jvmHeapWarning": 75,
"jvmHeapCritical": 90,
"failedDocsWarning": 1,
"failedDocsCritical": 10
}
}
""";
ResponseEntity<String> response = restTemplate.exchange(
"/api/v1/admin/thresholds", HttpMethod.PUT,
new HttpEntity<>(json, securityHelper.authHeaders(adminJwt)),
String.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
}
}