test(deploy): lock in FAILED→null snapshot for health-check-fail path
Existing IT only exercises the startContainer-throws path, where the exception bypasses the entire try block. Add a test where startContainer succeeds but getContainerStatus never returns healthy — this covers the early-exit at the HEALTH_CHECK stage, which is the common real-world failure shape and closest to the snapshot-write point. Shortens healthchecktimeout to 2s via @TestPropertySource so the test completes in a few seconds. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -20,6 +20,7 @@ import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
|
||||
@@ -34,8 +35,10 @@ import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* Verifies that DeploymentExecutor writes DeploymentConfigSnapshot on successful
|
||||
* RUNNING transition and does NOT write it on a FAILED path.
|
||||
* RUNNING transition and does NOT write it on a FAILED path (both the
|
||||
* startContainer-throws path and the health-check-fails path).
|
||||
*/
|
||||
@TestPropertySource(properties = "cameleer.server.runtime.healthchecktimeout=2")
|
||||
class DeploymentSnapshotIT extends AbstractPostgresIT {
|
||||
|
||||
@MockBean
|
||||
@@ -189,6 +192,53 @@ class DeploymentSnapshotIT extends AbstractPostgresIT {
|
||||
assertThat(failed.deployedConfigSnapshot()).isNull();
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Test 3: snapshot is NOT populated when the health check never passes.
|
||||
// This exercises the early-exit path in DeploymentExecutor (line ~231) —
|
||||
// startContainer succeeds, but no replica ever reports healthy, so
|
||||
// waitForAnyHealthy returns 0 before the snapshot-write point.
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
@Test
|
||||
void snapshot_isNotPopulated_whenHealthCheckFails() throws Exception {
|
||||
// --- given: container starts but never becomes healthy ---
|
||||
String fakeContainerId = "fake-unhealthy-" + UUID.randomUUID();
|
||||
|
||||
when(runtimeOrchestrator.isEnabled()).thenReturn(true);
|
||||
when(runtimeOrchestrator.startContainer(any())).thenReturn(fakeContainerId);
|
||||
when(runtimeOrchestrator.getContainerStatus(fakeContainerId))
|
||||
.thenReturn(new ContainerStatus("starting", true, 0, null));
|
||||
|
||||
String appSlug = "snap-unhealthy-" + UUID.randomUUID().toString().substring(0, 8);
|
||||
post("/api/v1/environments/default/apps", String.format("""
|
||||
{"slug": "%s", "displayName": "Snapshot Unhealthy App"}
|
||||
""", appSlug), operatorJwt);
|
||||
put("/api/v1/environments/default/apps/" + appSlug + "/container-config",
|
||||
"""
|
||||
{"runtimeType": "spring-boot", "appPort": 8081}
|
||||
""", operatorJwt);
|
||||
String versionId = uploadJar(appSlug, ("fake-jar-unhealthy-" + appSlug).getBytes());
|
||||
|
||||
// --- when: trigger deploy ---
|
||||
JsonNode deployResponse = post(
|
||||
"/api/v1/environments/default/apps/" + appSlug + "/deployments",
|
||||
String.format("{\"appVersionId\": \"%s\"}", versionId), operatorJwt);
|
||||
String deploymentId = deployResponse.path("id").asText();
|
||||
|
||||
// --- await FAILED (healthchecktimeout overridden to 2s in @TestPropertySource) ---
|
||||
await().atMost(30, TimeUnit.SECONDS)
|
||||
.pollInterval(500, TimeUnit.MILLISECONDS)
|
||||
.untilAsserted(() -> {
|
||||
Deployment d = deploymentRepository.findById(UUID.fromString(deploymentId))
|
||||
.orElseThrow(() -> new AssertionError("Deployment not found: " + deploymentId));
|
||||
assertThat(d.status()).isEqualTo(DeploymentStatus.FAILED);
|
||||
});
|
||||
|
||||
// --- then: snapshot is null (snapshot-write is gated behind health check) ---
|
||||
Deployment failed = deploymentRepository.findById(UUID.fromString(deploymentId)).orElseThrow();
|
||||
assertThat(failed.deployedConfigSnapshot()).isNull();
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Helpers
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user