Ctrl K
ComunidadGuías…

internal/baseline/store_test.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package baseline

import (
	"encoding/json"
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"reflect"
	"strconv"
	"strings"
	"sync"
	"testing"

	"github.com/sophotechlabs/spinoza/internal/checks"
)

const cluster = "https://one.example:6443"

func taken() checks.Baseline {
	return checks.Baseline{
		TakenAt: "2026-08-30T00:00:00Z",
		Scanned: 7,
		Checks:  []string{"privileged-containers"},
		Counts:  map[string]int{"privileged-containers": 2},
		Keys: map[string]string{
			"privileged-containers\x00aaaaaaaaaaa": "Deployment apps/api",
			"privileged-containers\x00bbbbbbbbbbb": "Deployment apps/web",
		},
	}
}

func store(t *testing.T) *Store {
	t.Helper()
	return Open(t.TempDir())
}

func TestABaselineComesBackAsItWasSaved(t *testing.T) {
	held := store(t)

	if err := held.Save(cluster, taken()); err != nil {
		t.Fatalf("save: %v", err)
	}
	back, ok := held.Load(cluster)

	if !ok {
		t.Fatal("a baseline that was just saved could not be read")
	}
	if back.TakenAt != taken().TakenAt || back.Scanned != 7 || back.Counts["privileged-containers"] != 2 {
		t.Fatalf("read back %+v", back)
	}
	if back.Keys["privileged-containers\x00aaaaaaaaaaa"] != "Deployment apps/api" || len(back.Keys) != 2 {
		t.Fatalf("read back %d keys: %v", len(back.Keys), back.Keys)
	}
}

func TestAnOlderStoredBaselineWithNoFingerprintsStillLoads(t *testing.T) {
	held := store(t)
	body := []byte(`{"takenAt":"2026-08-30T00:00:00Z","checks":["privileged-containers"]}`)
	if err := os.WriteFile(held.fileFor(cluster), body, 0o600); err != nil {
		t.Fatalf("write: %v", err)
	}

	back, ok := held.Load(cluster)

	if !ok {
		t.Fatal("an older baseline without fingerprints could not be read")
	}
	if back.Keys == nil {
		t.Fatal("an older baseline came back with a nil set to look fingerprints up in")
	}
}

func TestOneClusterCannotReadAnotherClusterBaseline(t *testing.T) {
	held := store(t)
	if err := held.Save(cluster, taken()); err != nil {
		t.Fatalf("save: %v", err)
	}

	if _, ok := held.Load("https://two.example:6443"); ok {
		t.Fatal("a second cluster was given the first one's baseline")
	}
}

func TestConcurrentClustersKeepTheirOwnBaselines(t *testing.T) {
	held := store(t)
	const clusters = 24
	var group sync.WaitGroup

	for index := range clusters {
		server := fmt.Sprintf("https://cluster-%02d.example:6443", index)
		baseline := taken()
		baseline.Cluster = server
		baseline.TakenAt = fmt.Sprintf("2026-09-01T%02d:00:00Z", index)
		group.Go(func() {
			if err := held.Save(server, baseline); err != nil {
				t.Errorf("save %s: %v", server, err)
			}
		})
	}
	group.Wait()

	for index := range clusters {
		server := fmt.Sprintf("https://cluster-%02d.example:6443", index)
		wantTakenAt := fmt.Sprintf("2026-09-01T%02d:00:00Z", index)
		baseline, ok := held.Load(server)
		if !ok {
			t.Fatalf("%s has no baseline", server)
		}
		if baseline.Cluster != server || baseline.TakenAt != wantTakenAt {
			t.Fatalf("%s loaded %+v", server, baseline)
		}
	}
}

func TestConcurrentReadsAndReplacementsNeverExposeAPartialBaseline(t *testing.T) {
	held := store(t)
	initial := taken()
	initial.Cluster = cluster
	if err := held.Save(cluster, initial); err != nil {
		t.Fatalf("initial save: %v", err)
	}

	const workers = 24
	var group sync.WaitGroup
	for index := range workers {
		group.Go(func() {
			next := taken()
			next.Cluster = cluster
			next.TakenAt = fmt.Sprintf("2026-09-01T%02d:00:00Z", index)
			if err := held.Save(cluster, next); err != nil {
				t.Errorf("save %d: %v", index, err)
			}
		})
		group.Go(func() {
			loaded, ok := held.Load(cluster)
			if !ok {
				t.Errorf("read %d saw no baseline", index)
				return
			}
			if loaded.Cluster != cluster || len(loaded.Checks) != 1 || len(loaded.Keys) != 2 {
				t.Errorf("read %d saw a partial baseline: %+v", index, loaded)
			}
		})
	}
	group.Wait()
}

func TestStoredBaselineAndDirectoryArePrivate(t *testing.T) {
	dir := filepath.Join(t.TempDir(), "baselines")
	held := Open(dir)

	if err := held.Save(cluster, taken()); err != nil {
		t.Fatalf("save: %v", err)
	}

	dirInfo, err := os.Stat(dir)
	if err != nil {
		t.Fatalf("stat directory: %v", err)
	}
	if dirInfo.Mode().Perm() != 0o700 {
		t.Fatalf("directory mode = %v, want 0700", dirInfo.Mode().Perm())
	}
	fileInfo, err := os.Stat(held.fileFor(cluster))
	if err != nil {
		t.Fatalf("stat baseline: %v", err)
	}
	if fileInfo.Mode().Perm() != 0o600 {
		t.Fatalf("baseline mode = %v, want 0600", fileInfo.Mode().Perm())
	}
}

func TestTakingANewBaselineReplacesTheOldOne(t *testing.T) {
	held := store(t)
	if err := held.Save(cluster, taken()); err != nil {
		t.Fatalf("save: %v", err)
	}
	next := taken()
	next.TakenAt = "2026-09-01T00:00:00Z"

	if err := held.Save(cluster, next); err != nil {
		t.Fatalf("save again: %v", err)
	}

	back, _ := held.Load(cluster)
	if back.TakenAt != next.TakenAt {
		t.Fatalf("the older baseline survived: %q", back.TakenAt)
	}
}

func TestForgettingABaselineLeavesNothingToCompareAgainst(t *testing.T) {
	held := store(t)
	if err := held.Save(cluster, taken()); err != nil {
		t.Fatalf("save: %v", err)
	}

	if err := held.Clear(cluster); err != nil {
		t.Fatalf("clear: %v", err)
	}

	if _, ok := held.Load(cluster); ok {
		t.Fatal("a forgotten baseline was still there")
	}
}

func TestForgettingABaselineThatWasNeverTakenIsNotAnError(t *testing.T) {
	if err := store(t).Clear(cluster); err != nil {
		t.Fatalf("clear: %v", err)
	}
}

func TestAClusterWithNoBaselineSaysSo(t *testing.T) {
	if _, ok := store(t).Load(cluster); ok {
		t.Fatal("a cluster with no baseline was given one")
	}
}

func TestAFileThatIsNotABaselineIsIgnoredRatherThanObeyed(t *testing.T) {
	dir := t.TempDir()
	held := Open(dir)
	if err := held.Save(cluster, taken()); err != nil {
		t.Fatalf("save: %v", err)
	}
	name, err := os.ReadDir(dir)
	if err != nil || len(name) != 1 {
		t.Fatalf("the store wrote %v (%v)", name, err)
	}
	if writeErr := os.WriteFile(filepath.Join(dir, name[0].Name()), []byte("{{{"), 0o600); writeErr != nil {
		t.Fatalf("write: %v", writeErr)
	}

	if _, ok := held.Load(cluster); ok {
		t.Fatal("an unreadable file was read as a baseline")
	}
}

func TestMoreFindingsThanOneBaselineHoldsIsRefused(t *testing.T) {
	huge := taken()
	huge.Keys = make(map[string]string, maxKeys+1)
	for at := range maxKeys + 1 {
		huge.Keys[strconv.Itoa(at)] = "Deployment apps/api"
	}

	if err := store(t).Save(cluster, huge); err == nil {
		t.Fatal("a baseline past the cap was written anyway")
	}
}

func TestABaselineTooLargeToReadBackIsNotSaved(t *testing.T) {
	held := store(t)
	huge := taken()
	huge.Keys = map[string]string{"finding": strings.Repeat("x", maxBytes)}

	if err := held.Save(cluster, huge); err == nil {
		t.Fatal("a baseline past the byte cap was written anyway")
	}
	if _, ok := held.Load(cluster); ok {
		t.Fatal("a refused baseline was left behind")
	}
}

func TestAnOversizedBaselineFileIsNotLoaded(t *testing.T) {
	held := store(t)
	path := held.fileFor(cluster)
	if err := os.WriteFile(path, []byte("{}"), 0o600); err != nil {
		t.Fatalf("write: %v", err)
	}
	if err := os.Truncate(path, maxBytes+1); err != nil {
		t.Fatalf("enlarge: %v", err)
	}

	if _, ok := held.Load(cluster); ok {
		t.Fatal("an oversized baseline file was loaded")
	}
}

func TestAStoreWithNoDirectoryKeepsNothingAndSaysNothing(t *testing.T) {
	held := Open("")

	if err := held.Save(cluster, taken()); err != nil {
		t.Fatalf("save: %v", err)
	}
	if err := held.Clear(cluster); err != nil {
		t.Fatalf("clear: %v", err)
	}
	if _, ok := held.Load(cluster); ok {
		t.Fatal("a store with nowhere to write produced a baseline")
	}
}

func TestADirectoryThatCannotBeMadeIsReported(t *testing.T) {
	blocked := filepath.Join(t.TempDir(), "a-file")
	if err := os.WriteFile(blocked, []byte("not a directory"), 0o600); err != nil {
		t.Fatalf("write: %v", err)
	}

	if err := Open(filepath.Join(blocked, "baselines")).Save(cluster, taken()); err == nil {
		t.Fatal("saving under a file succeeded")
	}
}

func TestADirectoryThatCannotBeWrittenToIsReported(t *testing.T) {
	dir := filepath.Join(t.TempDir(), "baselines")
	if err := os.MkdirAll(dir, 0o500); err != nil {
		t.Fatalf("mkdir: %v", err)
	}

	if err := Open(dir).Save(cluster, taken()); err == nil {
		t.Fatal("saving into a directory that refuses writes succeeded")
	}
}

func TestABaselineThatCannotBeRemovedIsReported(t *testing.T) {
	dir := t.TempDir()
	held := Open(dir)
	if err := os.MkdirAll(held.fileFor(cluster), 0o700); err != nil {
		t.Fatalf("mkdir: %v", err)
	}
	if err := os.WriteFile(filepath.Join(held.fileFor(cluster), "in-the-way"), []byte("x"), 0o600); err != nil {
		t.Fatalf("write: %v", err)
	}

	if err := held.Clear(cluster); err == nil {
		t.Fatal("clearing something that cannot be removed succeeded")
	}
}

func TestNoHomeMeansNoDefaultDirectory(t *testing.T) {
	t.Setenv("HOME", "")
	t.Setenv("XDG_CONFIG_HOME", "")

	if _, err := DefaultDir(); err == nil {
		t.Fatal("a machine with no config directory named one anyway")
	}
}

func TestTheDefaultDirectorySitsBesideTheSettings(t *testing.T) {
	dir, err := DefaultDir()
	if err != nil {
		t.Skipf("no user config directory here: %v", err)
	}
	if filepath.Base(dir) != "baselines" {
		t.Fatalf("the default directory is %q", dir)
	}
}

func TestABaselineComesBackFromTheFileItWasWrittenTo(t *testing.T) {
	body, err := Encode(taken())
	if err != nil {
		t.Fatalf("encode: %v", err)
	}

	back, decodeErr := Decode(body)

	if decodeErr != nil {
		t.Fatalf("decode: %v", decodeErr)
	}
	if back.TakenAt != taken().TakenAt || back.Scanned != 7 || len(back.Keys) != 2 {
		t.Fatalf("read back %+v", back)
	}
}

func TestABaselineRemembersWhichClusterItWasTakenOn(t *testing.T) {
	mine := taken()
	mine.Cluster = cluster
	body, err := Encode(mine)
	if err != nil {
		t.Fatalf("encode: %v", err)
	}

	back, decodeErr := Decode(body)

	if decodeErr != nil {
		t.Fatalf("decode: %v", decodeErr)
	}
	if back.Cluster != cluster {
		t.Fatalf("the baseline came back naming %q", back.Cluster)
	}
}

func TestSomethingThatIsNotABaselineIsRefusedRatherThanStored(t *testing.T) {
	cases := []struct{ name, body string }{
		{"not json", "{{{"},
		{"an empty object", "{}"},
		{"a baseline with no checks", `{"takenAt":"2026-08-30T00:00:00Z","checks":[]}`},
		{"a baseline with no day", `{"checks":["a"]}`},
	}
	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			if _, err := Decode([]byte(tc.body)); err == nil {
				t.Fatal("it was read as a baseline")
			}
		})
	}
}

func TestMalformedBaselineShapesAreRefused(t *testing.T) {
	cases := []struct {
		name      string
		body      string
		want      string
		alternate string
	}{
		{name: "empty", body: "", want: "EOF"},
		{name: "top level list", body: `[]`, want: "top-level value is not an object"},
		{name: "unknown field", body: `{"unknown":1}`, want: `unknown field "unknown"`},
		{name: "cluster type", body: `{"cluster":1}`, want: "cannot unmarshal number"},
		{name: "trailing value", body: `{} {}`, want: "baseline has a trailing value"},
		{name: "broken trailing value", body: `{} {`, want: "unexpected EOF"},
		{name: "checks opening", body: `{"checks":`, want: "EOF"},
		{name: "checks shape", body: `{"checks":{}}`, want: "baseline checks are not a list"},
		{name: "check type", body: `{"checks":[{}]}`, want: "cannot unmarshal object"},
		{name: "checks closing", body: `{"checks":["one"`, want: "EOF", alternate: "unexpected end of JSON input"},
		{name: "counts opening", body: `{"counts":`, want: "EOF"},
		{name: "counts shape", body: `{"counts":[]}`, want: "baseline counts are not an object"},
		{name: "count type", body: `{"counts":{"one":"bad"}}`, want: "cannot unmarshal string"},
		{name: "counts closing", body: `{"counts":{"one":1`, want: "EOF", alternate: "unexpected end of JSON input"},
		{name: "keys opening", body: `{"keys":`, want: "EOF"},
		{name: "keys shape", body: `{"keys":[]}`, want: "baseline keys are not an object"},
		{name: "key value type", body: `{"keys":{"one":1}}`, want: "cannot unmarshal number"},
		{name: "keys closing", body: `{"keys":{"one":"value"`, want: "EOF", alternate: "unexpected end of JSON input"},
	}
	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			found, err := Decode([]byte(tc.body))
			if err == nil {
				t.Fatal("malformed baseline was accepted")
			}
			if !reflect.DeepEqual(found, checks.Baseline{}) {
				t.Fatalf("baseline = %+v, want no partial document after refusal", found)
			}
			if errors.Is(err, ErrRead) {
				t.Fatalf("error = %v, want malformed input distinguished from an interrupted read", err)
			}
			if strings.Contains(err.Error(), tc.want) {
				return
			}
			if tc.alternate != "" {
				if strings.Contains(err.Error(), tc.alternate) {
					return
				}
			}
			t.Fatalf("error = %q, want %q or %q", err, tc.want, tc.alternate)
		})
	}
}

func TestNullOptionalBaselineMapsBecomeEmptyMaps(t *testing.T) {
	body := []byte(`{"takenAt":"2026-09-03T12:00:00Z","checks":["one"],"counts":null,"keys":null}`)

	baseline, err := Decode(body)
	if err != nil {
		t.Fatalf("decode: %v", err)
	}
	if baseline.Counts == nil {
		t.Fatal("counts remained nil")
	}
	if baseline.Keys == nil {
		t.Fatal("keys remained nil")
	}
}

func TestABaselineTooLargeToBeOneIsRefused(t *testing.T) {
	if _, err := Decode(make([]byte, maxBytes+1)); err == nil {
		t.Fatal("a body past the cap was read as a baseline")
	}
}

func TestAnImportedBaselineCannotExceedTheFindingCap(t *testing.T) {
	huge := taken()
	huge.Keys = make(map[string]string, maxKeys+1)
	for at := range maxKeys + 1 {
		huge.Keys[strconv.Itoa(at)] = "Deployment apps/api"
	}
	body, err := json.Marshal(flatten(huge))
	if err != nil {
		t.Fatalf("marshal: %v", err)
	}

	if _, err := Decode(body); err == nil {
		t.Fatal("a baseline past the finding cap was imported")
	}
}

func TestAnImportedBaselineAcceptsTheFindingCap(t *testing.T) {
	held := taken()
	held.Keys = make(map[string]string, maxKeys)
	for at := range maxKeys {
		held.Keys[strconv.Itoa(at)] = "Deployment apps/api"
	}
	body, err := json.Marshal(flatten(held))
	if err != nil {
		t.Fatalf("marshal: %v", err)
	}
	back, err := Decode(body)
	if err != nil {
		t.Fatalf("decode the maximum finding set: %v", err)
	}
	if len(back.Keys) != maxKeys {
		t.Fatalf("findings = %d, want %d", len(back.Keys), maxKeys)
	}
}

func TestImportedBaselineKeyContentHasAnExactByteLimit(t *testing.T) {
	accepted := taken()
	accepted.Keys = map[string]string{"k": strings.Repeat("x", maxKeyValueBytes-1)}
	body, err := json.Marshal(flatten(accepted))
	if err != nil {
		t.Fatalf("marshal accepted baseline: %v", err)
	}
	if _, decodeErr := Decode(body); decodeErr != nil {
		t.Fatalf("decode the maximum key content: %v", decodeErr)
	}
	rejected := taken()
	rejected.Keys = map[string]string{"k": strings.Repeat("x", maxKeyValueBytes)}
	body, err = json.Marshal(flatten(rejected))
	if err != nil {
		t.Fatalf("marshal rejected baseline: %v", err)
	}
	if _, err := Decode(body); err == nil || !strings.Contains(err.Error(), "keys and values exceed") {
		t.Fatalf("one extra content byte returned %v", err)
	}
}

func TestImportedBaselineAcceptsTheExactFileByteLimit(t *testing.T) {
	body, err := json.Marshal(flatten(taken()))
	if err != nil {
		t.Fatalf("marshal baseline: %v", err)
	}
	body = append(body, []byte(strings.Repeat(" ", maxBytes-len(body)))...)
	if _, err := Decode(body); err != nil {
		t.Fatalf("decode the maximum file size: %v", err)
	}
}

func TestImportedBaselineChecksAndCountsHaveExactEntryLimits(t *testing.T) {
	for _, tc := range []struct {
		name     string
		accepted stored
		rejected stored
	}{
		{
			name: "checks",
			accepted: stored{
				TakenAt: "2026-09-03T12:00:00Z",
				Checks:  numberedStrings(maxChecks),
			},
			rejected: stored{
				TakenAt: "2026-09-03T12:00:00Z",
				Checks:  numberedStrings(maxChecks + 1),
			},
		},
		{
			name: "counts",
			accepted: stored{
				TakenAt: "2026-09-03T12:00:00Z",
				Checks:  []string{"one"},
				Counts:  numberedCounts(maxCounts),
			},
			rejected: stored{
				TakenAt: "2026-09-03T12:00:00Z",
				Checks:  []string{"one"},
				Counts:  numberedCounts(maxCounts + 1),
			},
		},
	} {
		t.Run(tc.name, func(t *testing.T) {
			accepted, err := json.Marshal(tc.accepted)
			if err != nil {
				t.Fatalf("marshal accepted shape: %v", err)
			}
			if _, decodeErr := Decode(accepted); decodeErr != nil {
				t.Fatalf("decode accepted shape: %v", decodeErr)
			}
			rejected, err := json.Marshal(tc.rejected)
			if err != nil {
				t.Fatalf("marshal rejected shape: %v", err)
			}
			if _, err := Decode(rejected); err == nil {
				t.Fatal("one extra entry was accepted")
			}
		})
	}
}

func numberedStrings(count int) []string {
	out := make([]string, count)
	for at := range out {
		out[at] = strconv.Itoa(at)
	}
	return out
}

func numberedCounts(count int) map[string]int {
	out := make(map[string]int, count)
	for at := range count {
		out[strconv.Itoa(at)] = at
	}
	return out
}

func TestABaselineWithNoFingerprintsStillReads(t *testing.T) {
	back, err := Decode([]byte(`{"takenAt":"2026-08-30T00:00:00Z","checks":["a"]}`))
	if err != nil {
		t.Fatalf("decode: %v", err)
	}
	if back.Keys == nil {
		t.Fatal("a baseline with no fingerprints came back with a nil set to look them up in")
	}
}