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.HttpHeaders;
|
||||||
import org.springframework.http.HttpMethod;
|
import org.springframework.http.HttpMethod;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.test.context.TestPropertySource;
|
||||||
import org.springframework.util.LinkedMultiValueMap;
|
import org.springframework.util.LinkedMultiValueMap;
|
||||||
import org.springframework.util.MultiValueMap;
|
import org.springframework.util.MultiValueMap;
|
||||||
|
|
||||||
@@ -34,8 +35,10 @@ import static org.mockito.Mockito.when;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Verifies that DeploymentExecutor writes DeploymentConfigSnapshot on successful
|
* 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 {
|
class DeploymentSnapshotIT extends AbstractPostgresIT {
|
||||||
|
|
||||||
@MockBean
|
@MockBean
|
||||||
@@ -189,6 +192,53 @@ class DeploymentSnapshotIT extends AbstractPostgresIT {
|
|||||||
assertThat(failed.deployedConfigSnapshot()).isNull();
|
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
|
// Helpers
|
||||||
// -----------------------------------------------------------------------
|
// -----------------------------------------------------------------------
|
||||||
|
|||||||
Reference in New Issue
Block a user