tests: cover depends_on chaining and OpRunProvider stringer

Two coverage gaps surfaced by an external review:

- TestReconcileContainers_DependsOnChain: asserts that a service B with
  depends_on: [A] produces a CreateContainer for B that depends on A's
  last plan node (the serviceNodes mechanism in infrastructureDeps).
  This was the only depends_on-via-plan-DAG behavior untested before.

- TestReconcileContainers_DependsOnScaleDown: companion test that
  exercises the scale-down → dependent path specifically, verifying
  that the previous commit's lastNode-on-scale-down fix actually wires
  the dependency through.

- TestOperationTypeString: adds OpRunProvider to the table; all other
  OperationType values were already covered.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: Guillaume Lours <glours@users.noreply.github.com>
This commit is contained in:
Guillaume Lours 2026-05-28 17:00:05 +02:00 • committed by Guillaume Lours
parent a06368c333
commit 750eb9b24e
2 changed files with 85 additions and 0 deletions

View file

@ -38,6 +38,7 @@ func TestOperationTypeString(t *testing.T) {
{OpStopContainer, "StopContainer"},
{OpRemoveContainer, "RemoveContainer"},
{OpRenameContainer, "RenameContainer"},
{OpRunProvider, "RunProvider"},
{OperationType(999), "Unknown(999)"},
}
for _, tt := range tests {

View file

@ -582,6 +582,90 @@ func TestReconcileContainers_ExitedIsNoop(t *testing.T) {
assert.Assert(t, plan.IsEmpty())
}
// TestReconcileContainers_DependsOnChain verifies that a dependent service's
// container creation depends on the last plan node of the service it depends
// on (via reconciler.serviceNodes). Without this, services declared in
// depends_on could start before their dependencies' operations complete.
func TestReconcileContainers_DependsOnChain(t *testing.T) {
project := &types.Project{
Name: "myproject",
Services: types.Services{
"db": {Name: "db", Scale: intPtr(1)},
"web": {
Name: "web",
Scale: intPtr(1),
DependsOn: types.DependsOnConfig{
"db": {Condition: types.ServiceConditionStarted},
},
},
},
}
observed := &ObservedState{
ProjectName: "myproject",
Containers: map[string][]ObservedContainer{},
Networks: map[string]ObservedNetwork{},
Volumes: map[string]ObservedVolume{},
}
plan, err := reconcile(t.Context(), project, observed, defaultReconcileOptions(), noPrompt)
assert.NilError(t, err)
// db has no dependencies, so its CreateContainer has no deps.
// web depends on db, so its CreateContainer must depend on db's node #1.
assert.Equal(t, plan.String(), strings.TrimSpace(`
[] -> #1 service:db:1, CreateContainer, no existing container
[1] -> #2 service:web:1, CreateContainer, no existing container
`)+"\n")
}
// TestReconcileContainers_DependsOnScaleDown verifies that scale-down of a
// service still propagates through serviceNodes, so a dependent service waits
// for the scale-down's RemoveContainer to finish before starting its own ops.
func TestReconcileContainers_DependsOnScaleDown(t *testing.T) {
svc := types.ServiceConfig{Name: "db", Scale: intPtr(0)}
hash := mustServiceHash(t, svc)
project := &types.Project{
Name: "myproject",
Services: types.Services{
"db": svc,
"web": {
Name: "web",
Scale: intPtr(1),
DependsOn: types.DependsOnConfig{
"db": {Condition: types.ServiceConditionStarted},
},
},
},
}
observed := &ObservedState{
ProjectName: "myproject",
Containers: map[string][]ObservedContainer{
"db": {{
ID: "c1", Number: 1, State: container.StateRunning, ConfigHash: hash,
Summary: container.Summary{
ID: "c1", State: container.StateRunning,
Labels: map[string]string{api.ServiceLabel: "db", api.ContainerNumberLabel: "1", api.ConfigHashLabel: hash},
},
}},
},
Networks: map[string]ObservedNetwork{},
Volumes: map[string]ObservedVolume{},
}
plan, err := reconcile(t.Context(), project, observed, defaultReconcileOptions(), noPrompt)
assert.NilError(t, err)
// db scales 1→0: Stop+Remove. web's CreateContainer must depend on the
// Remove (#2), proving serviceNodes is updated even when only scale-down
// happens for the dependency.
assert.Equal(t, plan.String(), strings.TrimSpace(`
[] -> #1 service:db:1, StopContainer, scale down
[1] -> #2 service:db:1, RemoveContainer, scale down
[2] -> #3 service:web:1, CreateContainer, no existing container
`)+"\n")
}
func TestReconcileOrphans(t *testing.T) {
project := &types.Project{
Name: "myproject",