diff --git a/cameleer-server-app/src/test/java/com/cameleer/server/app/alerting/controller/AlertControllerIT.java b/cameleer-server-app/src/test/java/com/cameleer/server/app/alerting/controller/AlertControllerIT.java index 73925fc5..742ba47b 100644 --- a/cameleer-server-app/src/test/java/com/cameleer/server/app/alerting/controller/AlertControllerIT.java +++ b/cameleer-server-app/src/test/java/com/cameleer/server/app/alerting/controller/AlertControllerIT.java @@ -442,6 +442,62 @@ class AlertControllerIT extends AbstractPostgresIT { assertThat(bothRead).as("read must appear with no read filter").isTrue(); } + @Test + void read_is_global_other_users_see_readAt_set() throws Exception { + // Seed an alert targeting BOTH users so the viewer's GET /{id} is visible + AlertInstance instance = new AlertInstance( + UUID.randomUUID(), null, null, envIdA, + AlertState.FIRING, AlertSeverity.WARNING, + Instant.now(), null, null, null, null, null, null, false, + 42.0, 1000.0, null, "Global read test", "Operator reads, viewer sees it", + List.of("test-operator", "test-viewer"), List.of(), List.of()); + instance = instanceRepo.save(instance); + + // Operator (user A) marks the alert as read + ResponseEntity readResp = restTemplate.exchange( + "/api/v1/environments/" + envSlugA + "/alerts/" + instance.id() + "/read", + HttpMethod.POST, + new HttpEntity<>(securityHelper.authHeaders(operatorJwt)), + String.class); + assertThat(readResp.getStatusCode()).isEqualTo(HttpStatus.OK); + + // Viewer (user B) fetches the same alert and must see readAt != null + ResponseEntity getResp = restTemplate.exchange( + "/api/v1/environments/" + envSlugA + "/alerts/" + instance.id(), + HttpMethod.GET, + new HttpEntity<>(securityHelper.authHeadersNoBody(viewerJwt)), + String.class); + assertThat(getResp.getStatusCode()).isEqualTo(HttpStatus.OK); + JsonNode node = objectMapper.readTree(getResp.getBody()); + assertThat(node.path("readAt").isNull()) + .as("viewer must see readAt as non-null after operator marked read") + .isFalse(); + assertThat(node.path("readAt").isMissingNode()) + .as("readAt field must be present in response") + .isFalse(); + + // Viewer's list endpoint must also show the alert with readAt set + ResponseEntity listResp = restTemplate.exchange( + "/api/v1/environments/" + envSlugA + "/alerts", + HttpMethod.GET, + new HttpEntity<>(securityHelper.authHeadersNoBody(viewerJwt)), + String.class); + assertThat(listResp.getStatusCode()).isEqualTo(HttpStatus.OK); + JsonNode listBody = objectMapper.readTree(listResp.getBody()); + UUID instanceId = instance.id(); + boolean foundWithReadAt = false; + for (JsonNode item : listBody) { + if (item.path("id").asText().equals(instanceId.toString())) { + assertThat(item.path("readAt").isNull()) + .as("list entry readAt must be non-null for viewer after global read") + .isFalse(); + foundWithReadAt = true; + break; + } + } + assertThat(foundWithReadAt).as("alert must appear in viewer's list with readAt set").isTrue(); + } + // ------------------------------------------------------------------------- // Helpers // -------------------------------------------------------------------------