package jobspec import ( "os" "path/filepath" "strings" "testing" ) func TestDispatch_Markdown(t *testing.T) { input := "---\nkind: Job\nname: md-job\n---\nbody content\n" ws, err := Dispatch([]byte(input), "spec.md") if err != nil { t.Fatalf("Dispatch .md: %v", err) } if ws.Kind != "Job" { t.Errorf("Kind = %q, want Job", ws.Kind) } if ws.Name != "md-job" { t.Errorf("Name = %q, want md-job", ws.Name) } if ws.Body != "body content\n" { t.Errorf("Body = %q, want %q (R-015)", ws.Body, "body content\n") } } func TestDispatch_YAML(t *testing.T) { input := "kind: Service\nname: yaml-svc\nports:\n - name: http\n port: 80\n" ws, err := Dispatch([]byte(input), "spec.yaml") if err != nil { t.Fatalf("Dispatch .yaml: %v", err) } if ws.Kind != "Service" { t.Errorf("Kind = %q, want Service", ws.Kind) } if ws.Name != "yaml-svc" { t.Errorf("Name = %q, want yaml-svc", ws.Name) } if ws.Body != "" { t.Errorf("Body = %q, want empty (YAML has no body)", ws.Body) } if len(ws.Ports) != 1 || ws.Ports[0].Name != "http" || ws.Ports[0].Port != 80 { t.Errorf("Ports = %+v, want one http:80", ws.Ports) } } func TestDispatch_YML(t *testing.T) { input := "kind: DaemonSet\nname: yml-ds\n" ws, err := Dispatch([]byte(input), "spec.yml") if err != nil { t.Fatalf("Dispatch .yml: %v", err) } if ws.Kind != "DaemonSet" { t.Errorf("Kind = %q, want DaemonSet", ws.Kind) } if ws.Body != "" { t.Errorf("Body = %q, want empty", ws.Body) } } func TestDispatch_HCLAdapter(t *testing.T) { hcl := `job "demo" {} task "build" { command = "/bin/echo" args = ["hello"] } ` ws, err := Dispatch([]byte(hcl), "spec.hcl") if err != nil { t.Fatalf("Dispatch .hcl: %v", err) } if ws.Kind != "Job" { t.Errorf("Kind = %q, want Job (adapter always sets Job)", ws.Kind) } if ws.Name != "demo" { t.Errorf("Name = %q, want demo (from spec.Job.Name)", ws.Name) } if ws.Runtime == nil { t.Fatal("Runtime is nil; adapter should populate from tasks[0]") } if ws.Runtime.OneOf != "process" { t.Errorf("Runtime.OneOf = %q, want process", ws.Runtime.OneOf) } if ws.Runtime.Command != "/bin/echo" { t.Errorf("Runtime.Command = %q, want /bin/echo (from tasks[0].Command)", ws.Runtime.Command) } if ws.Body != "" { t.Errorf("Body = %q, want empty (HCL has no body)", ws.Body) } } func TestDispatch_HCLAdapterNoTasks(t *testing.T) { hcl := `job "x" {}` _, err := Dispatch([]byte(hcl), "spec.hcl") if err == nil { t.Fatal("expected error for HCL with no tasks") } if !strings.Contains(err.Error(), "at least one task") { t.Errorf("error = %q, want it to contain 'at least one task'", err.Error()) } } func TestDispatch_UnknownExtension(t *testing.T) { _, err := Dispatch([]byte("kind: Job\nname: x\n"), "spec.json") if err == nil { t.Fatal("expected error for unknown extension, got nil") } if !strings.Contains(err.Error(), "unknown extension") { t.Errorf("error = %q, want it to contain 'unknown extension'", err.Error()) } } func TestDispatch_NoExtension(t *testing.T) { _, err := Dispatch([]byte("kind: Job\nname: x\n"), "spec") if err == nil { t.Fatal("expected error for no extension, got nil") } } func TestDispatch_EmptyYAML(t *testing.T) { _, err := Dispatch([]byte(""), "spec.yaml") if err == nil { t.Fatal("expected error for empty YAML, got nil") } } func TestParseFile_Markdown(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "spec.md") content := "---\nkind: Job\nname: file-md\n---\nbody\n" if err := os.WriteFile(path, []byte(content), 0o644); err != nil { t.Fatalf("write: %v", err) } ws, err := ParseFile(path) if err != nil { t.Fatalf("ParseFile .md: %v", err) } if ws.Kind != "Job" || ws.Name != "file-md" { t.Errorf("got Kind=%q Name=%q", ws.Kind, ws.Name) } if ws.Body != "body\n" { t.Errorf("Body = %q, want %q", ws.Body, "body\n") } } func TestParseFile_YAML(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "spec.yaml") content := "kind: Service\nname: file-yaml\n" if err := os.WriteFile(path, []byte(content), 0o644); err != nil { t.Fatalf("write: %v", err) } ws, err := ParseFile(path) if err != nil { t.Fatalf("ParseFile .yaml: %v", err) } if ws.Kind != "Service" || ws.Name != "file-yaml" { t.Errorf("got Kind=%q Name=%q", ws.Kind, ws.Name) } if ws.Body != "" { t.Errorf("Body = %q, want empty", ws.Body) } } func TestParseFile_HCL(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "spec.hcl") content := `job "file-hcl" {} task "t" { command = "/bin/true" } ` if err := os.WriteFile(path, []byte(content), 0o644); err != nil { t.Fatalf("write: %v", err) } ws, err := ParseFile(path) if err != nil { t.Fatalf("ParseFile .hcl: %v", err) } if ws.Kind != "Job" || ws.Name != "file-hcl" { t.Errorf("got Kind=%q Name=%q", ws.Kind, ws.Name) } if ws.Runtime == nil || ws.Runtime.Command != "/bin/true" { t.Errorf("Runtime.Command = %v, want /bin/true", ws.Runtime) } } func TestParseFile_MissingFile(t *testing.T) { _, err := ParseFile(filepath.Join(t.TempDir(), "nope.md")) if err == nil { t.Fatal("expected error for missing file, got nil") } if !strings.Contains(err.Error(), "read spec file") { t.Errorf("error = %q, want it to contain 'read spec file'", err.Error()) } } func TestParseFile_UnknownExtension(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "spec.txt") if err := os.WriteFile(path, []byte("kind: Job\nname: x\n"), 0o644); err != nil { t.Fatalf("write: %v", err) } _, err := ParseFile(path) if err == nil { t.Fatal("expected error for unknown extension, got nil") } if !strings.Contains(err.Error(), "unknown extension") { t.Errorf("error = %q, want 'unknown extension'", err.Error()) } } func TestParseHCL_LegacySpec(t *testing.T) { hcl := `job "legacy" {} task "t" { command = "/bin/echo" } ` ws, err := ParseHCL([]byte(hcl), "spec.hcl") if err != nil { t.Fatalf("ParseHCL: %v", err) } if ws.Kind != "Job" || ws.Name != "legacy" { t.Errorf("adapter got Kind=%q Name=%q", ws.Kind, ws.Name) } }