Files
puppet-catalog-tools/cmd/catalog-impact/main_test.go
T
unkin-agent 8093933500
ci/woodpecker/pr/build Pipeline was successful
ci/woodpecker/pr/pre-commit Pipeline was successful
ci/woodpecker/pr/test Pipeline was successful
add the catalog-impact binary
Work out which hosts a puppet-prod change can affect, so a catalog test can
fan out over exactly those hosts instead of the whole estate.

- add cmd/catalog-impact and internal/impact
- map hieradata paths onto fact selectors from the hiera hierarchy
- trace manifest classes back to role classes through include, contain,
  class {} and hiera_include, to a fixed point
- fetch the estate fact table from pdbmux in one query via FactsByName
- widen to all hosts, loudly, for any path that matches no rule
- ship the binary and its completions in the RPM
2026-09-12 16:15:00 +10:00

90 lines
3.0 KiB
Go

package main
import (
"bytes"
"context"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
)
const estateBody = `[
{"certname":"web1.example.net","name":"enc_role","value":"roles::apps::media::jellyfin"},
{"certname":"web1.example.net","name":"enc_role_tier1","value":"apps"},
{"certname":"web1.example.net","name":"enc_role_tier2","value":"media"},
{"certname":"web1.example.net","name":"enc_role_tier3","value":"jellyfin"},
{"certname":"web1.example.net","name":"country","value":"au"},
{"certname":"web1.example.net","name":"region","value":"syd1"},
{"certname":"web1.example.net","name":"virtual","value":"lxc"},
{"certname":"web1.example.net","name":"os","value":{"name":"AlmaLinux","release":{"major":"9","minor":"7"}}},
{"certname":"k8s1.example.net","name":"enc_role","value":"roles::infra::k8s::node"},
{"certname":"k8s1.example.net","name":"enc_role_tier1","value":"infra"},
{"certname":"k8s1.example.net","name":"enc_role_tier2","value":"k8s"},
{"certname":"k8s1.example.net","name":"enc_role_tier3","value":"node"},
{"certname":"k8s1.example.net","name":"country","value":"au"},
{"certname":"k8s1.example.net","name":"region","value":"syd1"},
{"certname":"k8s1.example.net","name":"virtual","value":"physical"},
{"certname":"k8s1.example.net","name":"os","value":{"name":"AlmaLinux","release":{"major":"9","minor":"8"}}}
]`
func fakePDB(t *testing.T) string {
t.Helper()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(estateBody))
}))
t.Cleanup(srv.Close)
return srv.URL
}
func TestRunWithExplicitFileList(t *testing.T) {
var out bytes.Buffer
opts := &options{
repo: "../../internal/impact/testdata/repo",
base: "origin/develop",
files: []string{"modules/jellyfin/manifests/install.pp"},
pdb: fakePDB(t),
timeout: 5 * time.Second,
}
if err := run(context.Background(), &out, opts); err != nil {
t.Fatalf("run: %v", err)
}
if got, want := out.String(), "web1.example.net\n"; got != want {
t.Errorf("output = %q, want %q", got, want)
}
}
func TestRunExplain(t *testing.T) {
var out bytes.Buffer
opts := &options{
repo: "../../internal/impact/testdata/repo",
base: "origin/develop",
files: []string{"hieradata/common.yaml"},
pdb: fakePDB(t),
explain: true,
timeout: 5 * time.Second,
}
if err := run(context.Background(), &out, opts); err != nil {
t.Fatalf("run: %v", err)
}
if !strings.Contains(out.String(), "the whole estate is affected") {
t.Errorf("explain output = %q", out.String())
}
}
func TestRunRejectsConflictingOutputFlags(t *testing.T) {
opts := &options{jsonOut: true, explain: true}
if err := run(context.Background(), &bytes.Buffer{}, opts); err == nil {
t.Fatal("expected an error for --json with --explain")
}
}
func TestRunRejectsNegativeLimit(t *testing.T) {
opts := &options{limit: -1}
if err := run(context.Background(), &bytes.Buffer{}, opts); err == nil {
t.Fatal("expected an error for a negative --limit")
}
}