package emitter import ( "errors" "strings" "testing" "git.cloudinit.dev/coreci/orca/internal/jobspec" ) // mockEmitter is a test-only Emitter that returns a fixed File slice // (or an error) so the Registry tests do not depend on the // SystemdEmitter. Implements Emitter via value receiver. type mockEmitter struct { files []File err error } func (m mockEmitter) Render(spec *jobspec.WorkloadSpec, node *Node) ([]File, error) { if m.err != nil { return nil, m.err } out := make([]File, len(m.files)) copy(out, m.files) return out, nil } func TestRegistry_RegisterAndRender(t *testing.T) { r := NewRegistry() want := []File{{Path: "/tmp/a", Content: "alpha", Mode: "0644"}} r.Register("job:process", mockEmitter{files: want}) spec := &jobspec.WorkloadSpec{ Kind: "Job", Name: "demo", Runtime: &jobspec.RuntimeBlock{OneOf: "process", Command: "/bin/true"}, } node := &Node{Hostname: "node-1", Runtime: []string{"process"}} got, err := r.Render(spec, node) if err != nil { t.Fatalf("Render: %v", err) } if len(got) != 1 { t.Fatalf("got %d files, want 1", len(got)) } if got[0] != want[0] { t.Errorf("file = %+v, want %+v", got[0], want[0]) } } func TestRegistry_UnknownKindRuntime(t *testing.T) { r := NewRegistry() spec := &jobspec.WorkloadSpec{ Kind: "Service", Name: "web", Runtime: &jobspec.RuntimeBlock{OneOf: "wasm"}, } _, err := r.Render(spec, &Node{}) if err == nil { t.Fatal("expected error for unknown kind:runtime, got nil") } if !strings.Contains(err.Error(), "no emitter registered") { t.Errorf("error = %q, want 'no emitter registered'", err.Error()) } if !strings.Contains(err.Error(), "service:wasm") { t.Errorf("error = %q, want it to mention 'service:wasm'", err.Error()) } } func TestRegistry_MultipleEmittersCorrectSelected(t *testing.T) { r := NewRegistry() jobFiles := []File{{Path: "/tmp/job", Content: "job", Mode: "0644"}} svcFiles := []File{{Path: "/tmp/svc", Content: "svc", Mode: "0644"}} dsFiles := []File{{Path: "/tmp/ds", Content: "ds", Mode: "0644"}} r.Register("job:process", mockEmitter{files: jobFiles}) r.Register("service:process", mockEmitter{files: svcFiles}) r.Register("daemonset:process", mockEmitter{files: dsFiles}) cases := []struct { kind string runtime string wantPath string }{ {"Job", "process", "/tmp/job"}, {"Service", "process", "/tmp/svc"}, {"DaemonSet", "process", "/tmp/ds"}, } for _, tc := range cases { t.Run(tc.kind+":"+tc.runtime, func(t *testing.T) { spec := &jobspec.WorkloadSpec{ Kind: tc.kind, Name: "x", Runtime: &jobspec.RuntimeBlock{OneOf: tc.runtime, Command: "/bin/x"}, } got, err := r.Render(spec, &Node{Hostname: "n"}) if err != nil { t.Fatalf("Render: %v", err) } if len(got) != 1 { t.Fatalf("got %d files, want 1", len(got)) } if got[0].Path != tc.wantPath { t.Errorf("path = %q, want %q", got[0].Path, tc.wantPath) } }) } } func TestRegistry_NilSpec(t *testing.T) { r := NewRegistry() _, err := r.Render(nil, &Node{}) if err == nil { t.Fatal("expected error for nil spec") } if !strings.Contains(err.Error(), "spec is nil") { t.Errorf("error = %q, want 'spec is nil'", err.Error()) } } func TestRegistry_EmptyKind(t *testing.T) { r := NewRegistry() spec := &jobspec.WorkloadSpec{Runtime: &jobspec.RuntimeBlock{OneOf: "process"}} _, err := r.Render(spec, &Node{}) if err == nil { t.Fatal("expected error for empty kind") } if !strings.Contains(err.Error(), "kind is empty") { t.Errorf("error = %q, want 'kind is empty'", err.Error()) } } func TestRegistry_NilRuntime(t *testing.T) { r := NewRegistry() spec := &jobspec.WorkloadSpec{Kind: "Job", Name: "x"} _, err := r.Render(spec, &Node{}) if err == nil { t.Fatal("expected error for nil runtime") } if !strings.Contains(err.Error(), "runtime is nil") { t.Errorf("error = %q, want 'runtime is nil'", err.Error()) } } func TestRegistry_EmitterErrorPropagates(t *testing.T) { r := NewRegistry() wantErr := errors.New("boom") r.Register("job:process", mockEmitter{err: wantErr}) spec := &jobspec.WorkloadSpec{ Kind: "Job", Name: "x", Runtime: &jobspec.RuntimeBlock{OneOf: "process", Command: "/bin/x"}, } _, err := r.Render(spec, &Node{}) if !errors.Is(err, wantErr) { t.Errorf("err = %v, want %v", err, wantErr) } } // Compile-time assertion that mockEmitter and SystemdEmitter implement // Emitter. var ( _ Emitter = mockEmitter{} _ Emitter = SystemdEmitter{} )