New package internal/jobdef mirrors the schema package pattern: - Load/LoadAll/Validate for YAML job definitions - Supports trigger types: revision_created, bom_changed, manual, schedule - Supports scope types: item, assembly, project - Supports compute types: validate, rebuild, diff, export, custom - Defaults: timeout=600s, max_retries=1, priority=100 Example definitions in jobdefs/: - assembly-validate.yaml: incremental validation on revision_created - part-export-step.yaml: STEP export on manual trigger 11 unit tests, all passing.
329 lines
6.9 KiB
Go
329 lines
6.9 KiB
Go
package jobdef
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
)
|
|
|
|
func TestLoadValid(t *testing.T) {
|
|
dir := t.TempDir()
|
|
content := `
|
|
job:
|
|
name: test-job
|
|
version: 1
|
|
description: "A test job"
|
|
trigger:
|
|
type: manual
|
|
scope:
|
|
type: item
|
|
compute:
|
|
type: validate
|
|
command: create-validate
|
|
runner:
|
|
tags: [create]
|
|
timeout: 300
|
|
max_retries: 2
|
|
priority: 50
|
|
`
|
|
path := filepath.Join(dir, "test-job.yaml")
|
|
if err := os.WriteFile(path, []byte(content), 0644); err != nil {
|
|
t.Fatalf("writing test file: %v", err)
|
|
}
|
|
|
|
def, err := Load(path)
|
|
if err != nil {
|
|
t.Fatalf("Load: %v", err)
|
|
}
|
|
|
|
if def.Name != "test-job" {
|
|
t.Errorf("name = %q, want %q", def.Name, "test-job")
|
|
}
|
|
if def.Version != 1 {
|
|
t.Errorf("version = %d, want 1", def.Version)
|
|
}
|
|
if def.Trigger.Type != "manual" {
|
|
t.Errorf("trigger type = %q, want %q", def.Trigger.Type, "manual")
|
|
}
|
|
if def.Scope.Type != "item" {
|
|
t.Errorf("scope type = %q, want %q", def.Scope.Type, "item")
|
|
}
|
|
if def.Compute.Type != "validate" {
|
|
t.Errorf("compute type = %q, want %q", def.Compute.Type, "validate")
|
|
}
|
|
if def.Compute.Command != "create-validate" {
|
|
t.Errorf("compute command = %q, want %q", def.Compute.Command, "create-validate")
|
|
}
|
|
if len(def.Runner.Tags) != 1 || def.Runner.Tags[0] != "create" {
|
|
t.Errorf("runner tags = %v, want [create]", def.Runner.Tags)
|
|
}
|
|
if def.Timeout != 300 {
|
|
t.Errorf("timeout = %d, want 300", def.Timeout)
|
|
}
|
|
if def.MaxRetries != 2 {
|
|
t.Errorf("max_retries = %d, want 2", def.MaxRetries)
|
|
}
|
|
if def.Priority != 50 {
|
|
t.Errorf("priority = %d, want 50", def.Priority)
|
|
}
|
|
}
|
|
|
|
func TestLoadDefaults(t *testing.T) {
|
|
dir := t.TempDir()
|
|
content := `
|
|
job:
|
|
name: minimal
|
|
trigger:
|
|
type: manual
|
|
scope:
|
|
type: item
|
|
compute:
|
|
type: custom
|
|
command: do-something
|
|
`
|
|
path := filepath.Join(dir, "minimal.yaml")
|
|
if err := os.WriteFile(path, []byte(content), 0644); err != nil {
|
|
t.Fatalf("writing test file: %v", err)
|
|
}
|
|
|
|
def, err := Load(path)
|
|
if err != nil {
|
|
t.Fatalf("Load: %v", err)
|
|
}
|
|
|
|
if def.Timeout != 600 {
|
|
t.Errorf("default timeout = %d, want 600", def.Timeout)
|
|
}
|
|
if def.MaxRetries != 1 {
|
|
t.Errorf("default max_retries = %d, want 1", def.MaxRetries)
|
|
}
|
|
if def.Priority != 100 {
|
|
t.Errorf("default priority = %d, want 100", def.Priority)
|
|
}
|
|
if def.Version != 1 {
|
|
t.Errorf("default version = %d, want 1", def.Version)
|
|
}
|
|
}
|
|
|
|
func TestLoadInvalidTriggerType(t *testing.T) {
|
|
dir := t.TempDir()
|
|
content := `
|
|
job:
|
|
name: bad-trigger
|
|
trigger:
|
|
type: invalid_trigger
|
|
scope:
|
|
type: item
|
|
compute:
|
|
type: validate
|
|
command: create-validate
|
|
`
|
|
path := filepath.Join(dir, "bad.yaml")
|
|
if err := os.WriteFile(path, []byte(content), 0644); err != nil {
|
|
t.Fatalf("writing test file: %v", err)
|
|
}
|
|
|
|
_, err := Load(path)
|
|
if err == nil {
|
|
t.Fatal("expected error for invalid trigger type")
|
|
}
|
|
}
|
|
|
|
func TestLoadMissingName(t *testing.T) {
|
|
dir := t.TempDir()
|
|
content := `
|
|
job:
|
|
trigger:
|
|
type: manual
|
|
scope:
|
|
type: item
|
|
compute:
|
|
type: validate
|
|
command: create-validate
|
|
`
|
|
path := filepath.Join(dir, "no-name.yaml")
|
|
if err := os.WriteFile(path, []byte(content), 0644); err != nil {
|
|
t.Fatalf("writing test file: %v", err)
|
|
}
|
|
|
|
_, err := Load(path)
|
|
if err == nil {
|
|
t.Fatal("expected error for missing name")
|
|
}
|
|
}
|
|
|
|
func TestLoadMissingCommand(t *testing.T) {
|
|
dir := t.TempDir()
|
|
content := `
|
|
job:
|
|
name: no-command
|
|
trigger:
|
|
type: manual
|
|
scope:
|
|
type: item
|
|
compute:
|
|
type: validate
|
|
`
|
|
path := filepath.Join(dir, "no-cmd.yaml")
|
|
if err := os.WriteFile(path, []byte(content), 0644); err != nil {
|
|
t.Fatalf("writing test file: %v", err)
|
|
}
|
|
|
|
_, err := Load(path)
|
|
if err == nil {
|
|
t.Fatal("expected error for missing command")
|
|
}
|
|
}
|
|
|
|
func TestLoadAllDirectory(t *testing.T) {
|
|
dir := t.TempDir()
|
|
|
|
job1 := `
|
|
job:
|
|
name: job-one
|
|
trigger:
|
|
type: manual
|
|
scope:
|
|
type: item
|
|
compute:
|
|
type: validate
|
|
command: create-validate
|
|
`
|
|
job2 := `
|
|
job:
|
|
name: job-two
|
|
trigger:
|
|
type: revision_created
|
|
scope:
|
|
type: assembly
|
|
compute:
|
|
type: export
|
|
command: create-export
|
|
`
|
|
if err := os.WriteFile(filepath.Join(dir, "one.yaml"), []byte(job1), 0644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(dir, "two.yml"), []byte(job2), 0644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// Non-YAML file should be ignored
|
|
if err := os.WriteFile(filepath.Join(dir, "readme.txt"), []byte("ignore me"), 0644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
defs, err := LoadAll(dir)
|
|
if err != nil {
|
|
t.Fatalf("LoadAll: %v", err)
|
|
}
|
|
|
|
if len(defs) != 2 {
|
|
t.Fatalf("loaded %d definitions, want 2", len(defs))
|
|
}
|
|
if _, ok := defs["job-one"]; !ok {
|
|
t.Error("job-one not found")
|
|
}
|
|
if _, ok := defs["job-two"]; !ok {
|
|
t.Error("job-two not found")
|
|
}
|
|
}
|
|
|
|
func TestLoadAllEmptyDirectory(t *testing.T) {
|
|
dir := t.TempDir()
|
|
|
|
defs, err := LoadAll(dir)
|
|
if err != nil {
|
|
t.Fatalf("LoadAll: %v", err)
|
|
}
|
|
if len(defs) != 0 {
|
|
t.Errorf("loaded %d definitions from empty dir, want 0", len(defs))
|
|
}
|
|
}
|
|
|
|
func TestLoadWithFilter(t *testing.T) {
|
|
dir := t.TempDir()
|
|
content := `
|
|
job:
|
|
name: filtered-job
|
|
trigger:
|
|
type: revision_created
|
|
filter:
|
|
item_type: assembly
|
|
scope:
|
|
type: assembly
|
|
compute:
|
|
type: validate
|
|
command: create-validate
|
|
`
|
|
path := filepath.Join(dir, "filtered.yaml")
|
|
if err := os.WriteFile(path, []byte(content), 0644); err != nil {
|
|
t.Fatalf("writing test file: %v", err)
|
|
}
|
|
|
|
def, err := Load(path)
|
|
if err != nil {
|
|
t.Fatalf("Load: %v", err)
|
|
}
|
|
|
|
if def.Trigger.Filter["item_type"] != "assembly" {
|
|
t.Errorf("filter item_type = %q, want %q", def.Trigger.Filter["item_type"], "assembly")
|
|
}
|
|
}
|
|
|
|
func TestLoadWithArgs(t *testing.T) {
|
|
dir := t.TempDir()
|
|
content := `
|
|
job:
|
|
name: args-job
|
|
trigger:
|
|
type: manual
|
|
scope:
|
|
type: item
|
|
compute:
|
|
type: export
|
|
command: create-export
|
|
args:
|
|
format: step
|
|
include_mesh: true
|
|
`
|
|
path := filepath.Join(dir, "args.yaml")
|
|
if err := os.WriteFile(path, []byte(content), 0644); err != nil {
|
|
t.Fatalf("writing test file: %v", err)
|
|
}
|
|
|
|
def, err := Load(path)
|
|
if err != nil {
|
|
t.Fatalf("Load: %v", err)
|
|
}
|
|
|
|
if def.Compute.Args["format"] != "step" {
|
|
t.Errorf("args format = %v, want %q", def.Compute.Args["format"], "step")
|
|
}
|
|
if def.Compute.Args["include_mesh"] != true {
|
|
t.Errorf("args include_mesh = %v, want true", def.Compute.Args["include_mesh"])
|
|
}
|
|
}
|
|
|
|
func TestValidateInvalidScopeType(t *testing.T) {
|
|
d := &Definition{
|
|
Name: "test",
|
|
Trigger: TriggerConfig{Type: "manual"},
|
|
Scope: ScopeConfig{Type: "galaxy"},
|
|
Compute: ComputeConfig{Type: "validate", Command: "create-validate"},
|
|
}
|
|
if err := d.Validate(); err == nil {
|
|
t.Fatal("expected error for invalid scope type")
|
|
}
|
|
}
|
|
|
|
func TestValidateInvalidComputeType(t *testing.T) {
|
|
d := &Definition{
|
|
Name: "test",
|
|
Trigger: TriggerConfig{Type: "manual"},
|
|
Scope: ScopeConfig{Type: "item"},
|
|
Compute: ComputeConfig{Type: "teleport", Command: "beam-up"},
|
|
}
|
|
if err := d.Validate(); err == nil {
|
|
t.Fatal("expected error for invalid compute type")
|
|
}
|
|
}
|