package engine import ( "context" "testing" "git.cloudinit.dev/coreci/orca/internal/store" ) func TestPickNodeBestFit(t *testing.T) { caps := []*store.NodeCapacity{ {NodeID: "node-b", CPUMillicores: 1000, MemoryMiB: 1024, DiskMiB: 1024}, {NodeID: "node-a", CPUMillicores: 4000, MemoryMiB: 4096, DiskMiB: 4096}, {NodeID: "node-c", CPUMillicores: 500, MemoryMiB: 512, DiskMiB: 512}, } spec := JobSpec{CPUMillicores: 1000, MemoryMiB: 1024, DiskMiB: 1024} got, idx, err := PickNode(spec, caps) if err != nil { t.Fatalf("PickNode: %v", err) } if got.NodeID != "node-a" { t.Errorf("PickNode: got %s, want node-a (most free capacity)", got.NodeID) } if idx != 1 { t.Errorf("PickNode: got idx %d, want 1", idx) } } func TestPickNodeNoFit(t *testing.T) { caps := []*store.NodeCapacity{ {NodeID: "node-a", CPUMillicores: 100, MemoryMiB: 100, DiskMiB: 100}, } spec := JobSpec{CPUMillicores: 1000, MemoryMiB: 1024, DiskMiB: 1024} _, _, err := PickNode(spec, caps) if err == nil { t.Fatal("expected PickNode to fail when no node can fit") } } func TestPickNodeTieDeterministic(t *testing.T) { // Two nodes with identical free capacity. Tie broken by NodeID // (lexicographic) for determinism. caps := []*store.NodeCapacity{ {NodeID: "node-z", CPUMillicores: 4000, MemoryMiB: 4096, DiskMiB: 4096}, {NodeID: "node-a", CPUMillicores: 4000, MemoryMiB: 4096, DiskMiB: 4096}, } spec := JobSpec{CPUMillicores: 1000, MemoryMiB: 1024, DiskMiB: 1024} got, _, err := PickNode(spec, caps) if err != nil { t.Fatalf("PickNode: %v", err) } if got.NodeID != "node-a" { t.Errorf("PickNode tie-break: got %s, want node-a (lexicographic)", got.NodeID) } } func TestJobSpecFits(t *testing.T) { spec := JobSpec{CPUMillicores: 1000, MemoryMiB: 1024, DiskMiB: 1024} c := &store.NodeCapacity{CPUMillicores: 2000, MemoryMiB: 2048, DiskMiB: 2048} if !spec.Fits(c) { t.Error("Fits: should fit") } c.CPUMillicores = 500 if spec.Fits(c) { t.Error("Fits: should not fit (CPU too low)") } } func TestJobSpecFits_NilCapacity(t *testing.T) { spec := JobSpec{CPUMillicores: 1000} if spec.Fits(nil) { t.Error("Fits(nil): should be false") } } func TestJobSpecScore_NilCapacity(t *testing.T) { spec := JobSpec{CPUMillicores: 1000, MemoryMiB: 1024} if got := spec.Score(nil); got != -1 { t.Errorf("Score(nil) = %d, want -1", got) } } func TestJobSpecScore_OverCapacity(t *testing.T) { spec := JobSpec{CPUMillicores: 2000, MemoryMiB: 1024} c := &store.NodeCapacity{CPUMillicores: 1000, MemoryMiB: 2048} if got := spec.Score(c); got != -1 { t.Errorf("Score over CPU = %d, want -1", got) } c2 := &store.NodeCapacity{CPUMillicores: 4000, MemoryMiB: 512} if got := spec.Score(c2); got != -1 { t.Errorf("Score over Mem = %d, want -1", got) } } func TestJobSpecScore_Fits(t *testing.T) { spec := JobSpec{CPUMillicores: 1000, MemoryMiB: 1024} c := &store.NodeCapacity{CPUMillicores: 4000, MemoryMiB: 4096} got := spec.Score(c) want := int64((4000 - 1000) + (4096 - 1024)) if got != want { t.Errorf("Score = %d, want %d", got, want) } } func TestPickNode_Empty(t *testing.T) { _, _, err := PickNode(JobSpec{}, nil) if err == nil { t.Fatal("expected error for empty capacities") } } func TestMemLocalNode_Capacity(t *testing.T) { c := &store.NodeCapacity{NodeID: "self", CPUMillicores: 1000, MemoryMiB: 1024} ln := MemLocalNode(c) got, err := ln.Capacity(context.Background()) if err != nil { t.Fatalf("Capacity: %v", err) } if got != c { t.Errorf("Capacity: got %+v, want %+v", got, c) } } func TestMemLocalNode_NilCapacity(t *testing.T) { ln := MemLocalNode(nil) _, err := ln.Capacity(context.Background()) if err == nil { t.Fatal("expected error for nil capacity") } }