api(apps): GET /apps/{slug}/dirty-state returns desired-vs-deployed diff
Wires DirtyStateCalculator behind an HTTP endpoint on AppController. Adds findLatestSuccessfulByAppAndEnv to PostgresDeploymentRepository, registers DirtyStateCalculator as a Spring bean (with ObjectMapper for JavaTimeModule support), and covers all three scenarios with IT. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -16,11 +16,17 @@ import java.util.UUID;
|
||||
* Compares the app's current desired state (JAR + agent config + container config) to the
|
||||
* config snapshot from the last successful deployment, producing a structured dirty result.
|
||||
*
|
||||
* <p>Pure logic — no IO, no Spring. Safe to unit-test as a POJO.</p>
|
||||
* <p>Pure logic — no IO, no Spring. Safe to unit-test as a POJO.
|
||||
* Caller must supply an {@link ObjectMapper} configured with {@code JavaTimeModule} so that
|
||||
* {@code ApplicationConfig.updatedAt} (an {@link java.time.Instant}) serialises correctly.</p>
|
||||
*/
|
||||
public class DirtyStateCalculator {
|
||||
|
||||
private static final ObjectMapper MAPPER = new ObjectMapper();
|
||||
private final ObjectMapper mapper;
|
||||
|
||||
public DirtyStateCalculator(ObjectMapper mapper) {
|
||||
this.mapper = mapper;
|
||||
}
|
||||
|
||||
public DirtyStateResult compute(UUID desiredJarVersionId,
|
||||
ApplicationConfig desiredAgentConfig,
|
||||
@@ -38,10 +44,10 @@ public class DirtyStateCalculator {
|
||||
String.valueOf(desiredJarVersionId), String.valueOf(snapshot.jarVersionId())));
|
||||
}
|
||||
|
||||
compareJson("agentConfig", MAPPER.valueToTree(desiredAgentConfig),
|
||||
MAPPER.valueToTree(snapshot.agentConfig()), diffs);
|
||||
compareJson("containerConfig", MAPPER.valueToTree(desiredContainerConfig),
|
||||
MAPPER.valueToTree(snapshot.containerConfig()), diffs);
|
||||
compareJson("agentConfig", mapper.valueToTree(desiredAgentConfig),
|
||||
mapper.valueToTree(snapshot.agentConfig()), diffs);
|
||||
compareJson("containerConfig", mapper.valueToTree(desiredContainerConfig),
|
||||
mapper.valueToTree(snapshot.containerConfig()), diffs);
|
||||
|
||||
return new DirtyStateResult(!diffs.isEmpty(), diffs);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.cameleer.server.core.runtime;
|
||||
|
||||
import com.cameleer.common.model.ApplicationConfig;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Map;
|
||||
@@ -10,9 +11,11 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class DirtyStateCalculatorTest {
|
||||
|
||||
private static final DirtyStateCalculator CALC = new DirtyStateCalculator(new ObjectMapper());
|
||||
|
||||
@Test
|
||||
void noSnapshot_meansEverythingDirty() {
|
||||
DirtyStateCalculator calc = new DirtyStateCalculator();
|
||||
DirtyStateCalculator calc = CALC;
|
||||
|
||||
ApplicationConfig desiredAgent = new ApplicationConfig();
|
||||
desiredAgent.setSamplingRate(1.0);
|
||||
@@ -27,7 +30,7 @@ class DirtyStateCalculatorTest {
|
||||
|
||||
@Test
|
||||
void identicalSnapshot_isClean() {
|
||||
DirtyStateCalculator calc = new DirtyStateCalculator();
|
||||
DirtyStateCalculator calc = CALC;
|
||||
|
||||
ApplicationConfig cfg = new ApplicationConfig();
|
||||
cfg.setSamplingRate(0.5);
|
||||
@@ -43,7 +46,7 @@ class DirtyStateCalculatorTest {
|
||||
|
||||
@Test
|
||||
void differentJar_marksJarField() {
|
||||
DirtyStateCalculator calc = new DirtyStateCalculator();
|
||||
DirtyStateCalculator calc = CALC;
|
||||
ApplicationConfig cfg = new ApplicationConfig();
|
||||
Map<String, Object> container = Map.of();
|
||||
UUID v1 = UUID.randomUUID();
|
||||
@@ -59,7 +62,7 @@ class DirtyStateCalculatorTest {
|
||||
|
||||
@Test
|
||||
void differentSamplingRate_marksAgentField() {
|
||||
DirtyStateCalculator calc = new DirtyStateCalculator();
|
||||
DirtyStateCalculator calc = CALC;
|
||||
|
||||
ApplicationConfig deployedCfg = new ApplicationConfig();
|
||||
deployedCfg.setSamplingRate(0.5);
|
||||
@@ -77,7 +80,7 @@ class DirtyStateCalculatorTest {
|
||||
|
||||
@Test
|
||||
void differentContainerMemory_marksContainerField() {
|
||||
DirtyStateCalculator calc = new DirtyStateCalculator();
|
||||
DirtyStateCalculator calc = CALC;
|
||||
ApplicationConfig cfg = new ApplicationConfig();
|
||||
UUID jarId = UUID.randomUUID();
|
||||
DeploymentConfigSnapshot snap = new DeploymentConfigSnapshot(jarId, cfg, Map.of("memoryLimitMb", 512));
|
||||
@@ -91,7 +94,7 @@ class DirtyStateCalculatorTest {
|
||||
|
||||
@Test
|
||||
void nullAgentConfigInSnapshot_marksAgentConfigDiff() {
|
||||
DirtyStateCalculator calc = new DirtyStateCalculator();
|
||||
DirtyStateCalculator calc = CALC;
|
||||
ApplicationConfig desired = new ApplicationConfig();
|
||||
desired.setSamplingRate(1.0);
|
||||
UUID jarId = UUID.randomUUID();
|
||||
@@ -106,7 +109,7 @@ class DirtyStateCalculatorTest {
|
||||
|
||||
@Test
|
||||
void nestedAgentField_reportsDeepPath() {
|
||||
DirtyStateCalculator calc = new DirtyStateCalculator();
|
||||
DirtyStateCalculator calc = CALC;
|
||||
|
||||
ApplicationConfig deployed = new ApplicationConfig();
|
||||
deployed.setTracedProcessors(Map.of("proc-1", "DEBUG"));
|
||||
@@ -124,7 +127,7 @@ class DirtyStateCalculatorTest {
|
||||
|
||||
@Test
|
||||
void stringField_differenceValueIsUnquoted() {
|
||||
DirtyStateCalculator calc = new DirtyStateCalculator();
|
||||
DirtyStateCalculator calc = CALC;
|
||||
|
||||
ApplicationConfig deployed = new ApplicationConfig();
|
||||
deployed.setApplicationLogLevel("INFO");
|
||||
|
||||
Reference in New Issue
Block a user