package declaration import ( "strings" "testing" ) // The host refuses a schedule it does not fully understand, on arrival, for the same reason the // control plane refuses it near its author (novox/hq ADR 0053): a declaration half-understood is // refused whole rather than half-applied. func schedulePinned() string { return "registry.example/runtime@sha256:" + strings.Repeat("a", 64) } func TestAScheduledContainerCarriesTheCronField(t *testing.T) { d, err := Parse([]byte(`{"declaration":1,"resources":[ {"id":"sync","type":"container","name":"sync","image":"` + schedulePinned() + `","schedule":"0 3 * * *"} ]}`)) if err != nil { t.Fatalf("a valid scheduled container was refused: %v", err) } c, ok := d.Resources[0].(*Container) if !ok { t.Fatalf("the scheduled resource is not a container: %T", d.Resources[0]) } if c.Schedule != "0 3 * * *" { t.Errorf("the schedule did not survive parsing: %q", c.Schedule) } } func TestAMalformedScheduleIsRefused(t *testing.T) { _, err := Parse([]byte(`{"declaration":1,"resources":[ {"id":"sync","type":"container","name":"sync","image":"` + schedulePinned() + `","schedule":"every night"} ]}`)) if err == nil { t.Fatal("a container with a malformed schedule was accepted") } if !strings.Contains(err.Error(), "cron") && !strings.Contains(err.Error(), "field") { t.Errorf("refused for the wrong reason: %v", err) } } func TestAContainerCannotBeBothRunOnceAndScheduled(t *testing.T) { // A container runs once and gates, or on a cadence, or stays up — never two (novox/hq ADR 0053). _, err := Parse([]byte(`{"declaration":1,"resources":[ {"id":"sync","type":"container","name":"sync","image":"` + schedulePinned() + `","run-once":true,"schedule":"0 3 * * *"} ]}`)) if err == nil { t.Fatal("a container that was both run-once and scheduled was accepted") } if !strings.Contains(err.Error(), "run-once") && !strings.Contains(err.Error(), "scheduled") { t.Errorf("refused for the wrong reason: %v", err) } }