fix(landscape): bound node fan-out, cover override resolution, test the run consumer

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
2026-09-05 13:49:27 +05:00
parent 5fba77b59c
commit d74277552f
4 changed files with 188 additions and 8 deletions

View File

@@ -356,6 +356,10 @@ func (j *stubLPJobs) EnqueueRun(_ context.Context, runID uuid.UUID) error {
type stubComputeCall struct {
node string
inputs map[string]string
// bodies holds each input file's content at call time; seedDataset stores
// each object under its own storage key as its body, so a test can tell
// which dataset a node actually downloaded.
bodies map[string]string
params landscape.Params
}
@@ -370,12 +374,19 @@ type stubCompute struct {
func newStubCompute() *stubCompute { return &stubCompute{errs: map[string]error{}} }
func (c *stubCompute) fn(_ context.Context, _ raster.IO, node landscape.Node, inputs map[string]string, outDir string, p landscape.Params) (map[string]string, error) {
c.mu.Lock()
in := map[string]string{}
bodies := map[string]string{}
for k, v := range inputs {
in[k] = v
body, err := os.ReadFile(v)
if err != nil {
return nil, err
}
bodies[k] = string(body)
}
c.calls = append(c.calls, stubComputeCall{node: node.Code, inputs: in, params: p})
c.mu.Lock()
c.calls = append(c.calls, stubComputeCall{node: node.Code, inputs: in, bodies: bodies, params: p})
err := c.errs[node.Code]
c.mu.Unlock()
if err != nil {
@@ -512,7 +523,7 @@ func (f *lpFixture) seedDataset(t *testing.T, code, status string, fileType doma
if err != nil {
t.Fatalf("seed dataset %s: %v", code, err)
}
if err := f.store.Put(ctx, key, bytes.NewReader([]byte("TIF")), 3, "image/tiff"); err != nil {
if err := f.store.Put(ctx, key, bytes.NewReader([]byte(key)), int64(len(key)), "image/tiff"); err != nil {
t.Fatalf("seed object %s: %v", key, err)
}
return d
@@ -1159,3 +1170,49 @@ func TestLandscapeService_Execute_NodeFailureFailsRunAndCleansUp(t *testing.T) {
})
}
}
func TestLandscapeService_Execute_OverridePinsInputOverFresherDataset(t *testing.T) {
f := newLPFixture(t)
f.seedAllSlots(t)
// Two ready rasters in the same slot group: the pinned one is the older of
// the two, so resolving by freshness alone would pick the other.
pinned := f.seedDataset(t, landscape.SlotZLCR, domain.DatasetStatusReady, domain.FileTypeRaster, "2023-01-31")
fresher := f.seedDataset(t, landscape.SlotZLCR, domain.DatasetStatusReady, domain.FileTypeRaster, "2026-01-31")
run := f.runOnce(t, RunInput{Overrides: map[string]uuid.UUID{landscape.SlotZLCR: pinned.ID}})
if run.Status != domain.LPRunSucceeded {
t.Fatalf("status = %s (%v), want succeeded", run.Status, run.Error)
}
// Both Z nodes consume the slot, and both must record the pinned dataset.
for _, code := range []string{"00", "02"} {
got := refsByGroup(nodeByCode(t, run, code).Inputs)[landscape.SlotZLCR]
if got != pinned.ID {
if got == fresher.ID {
t.Fatalf("node %s resolved the fresher dataset %v, want the pinned %v", code, got, pinned.ID)
}
t.Fatalf("node %s input for %s = %v, want %v", code, landscape.SlotZLCR, got, pinned.ID)
}
}
// The compute stub was handed the pinned object's bytes, not the fresher one's.
call, ok := f.compute.callFor("00")
if !ok {
t.Fatal("compute was not called for node 00")
}
path := call.inputs[landscape.SlotZLCR]
if path == "" {
t.Fatalf("node 00 got no path for %s (%v)", landscape.SlotZLCR, call.inputs)
}
if got := call.bodies[landscape.SlotZLCR]; got != pinned.StorageKey {
t.Fatalf("node 00 downloaded %q, want the pinned dataset's object %q", got, pinned.StorageKey)
}
// A group with no override still resolves the freshest ready dataset.
newest := f.seedDataset(t, landscape.SlotUVeg, domain.DatasetStatusReady, domain.FileTypeRaster, "2026-02-28")
second := f.runOnce(t, RunInput{Overrides: map[string]uuid.UUID{landscape.SlotZLCR: pinned.ID}})
if got := refsByGroup(nodeByCode(t, second, "01").Inputs)[landscape.SlotUVeg]; got != newest.ID {
t.Fatalf("node 01 input for %s = %v, want the freshest %v", landscape.SlotUVeg, got, newest.ID)
}
}