// SPDX-License-Identifier: GPL-3.0-or-later package pkgdb import ( "os" "path/filepath" "testing" "time" "codeberg.org/anassaeneroi/enodia-sentinal/go-agent/internal/config" ) func write(t *testing.T, path, contents string) { t.Helper() if err := os.MkdirAll(filepath.Dir(path), 0o750); err != nil { t.Fatal(err) } if err := os.WriteFile(path, []byte(contents), 0o640); err != nil { t.Fatal(err) } } func TestFingerprintIsDeterministicAndChangesWithContent(t *testing.T) { directory := t.TempDir() write(t, filepath.Join(directory, "b", "desc"), "second") write(t, filepath.Join(directory, "a", "desc"), "first") first, err := Fingerprint(directory) if err != nil { t.Fatal(err) } again, err := Fingerprint(directory) if err != nil || first != again { t.Fatalf("fingerprints %q %q err=%v", first, again, err) } write(t, filepath.Join(directory, "a", "desc"), "changed") changed, err := Fingerprint(directory) if err != nil || changed == first { t.Fatalf("changed fingerprint %q original %q err=%v", changed, first, err) } } func TestLastTransactionTimeAcceptsPacmanTimestampLayouts(t *testing.T) { path := filepath.Join(t.TempDir(), "pacman.log") write(t, path, "[2026-07-24T10:00:00-0700] [ALPM] transaction started\n[2026-07-24T10:01:00-07:00] [ALPM] transaction completed\n") got, ok := LastTransactionTime(path) if !ok || got.Format(time.RFC3339) != "2026-07-24T10:01:00-07:00" { t.Fatalf("transaction=%v ok=%v", got, ok) } } func TestManagerPreservesUnexplainedChangedAnchorAndAcceptsTransaction(t *testing.T) { directory := t.TempDir() database := filepath.Join(directory, "pacman-db") write(t, filepath.Join(database, "pkg", "desc"), "trusted") cfg := config.Default() cfg.LogDir = filepath.Join(directory, "state") manager := New(cfg) manager.DBDir = database now := time.Date(2026, 7, 24, 12, 0, 0, 0, time.UTC) manager.Now = func() time.Time { return now } manager.LastTransaction = func(string) (time.Time, bool) { return time.Time{}, false } if err := manager.Initialize(); err != nil { t.Fatal(err) } initial, ok := loadAnchor(manager.anchorPath()) if !ok { t.Fatal("missing initial anchor") } write(t, filepath.Join(database, "pkg", "desc"), "tampered") if err := manager.evaluate(now.Add(time.Minute)); err != nil { t.Fatal(err) } alerts := manager.Drain() if len(alerts) != 1 || alerts[0].SID != SIDTamper || alerts[0].Severity != "CRITICAL" { t.Fatalf("alerts=%+v", alerts) } afterTamper, _ := loadAnchor(manager.anchorPath()) if afterTamper.Fingerprint != initial.Fingerprint { t.Fatal("unexplained change silently replaced anchor") } manager.LastTransaction = func(string) (time.Time, bool) { return now.Add(time.Minute), true } if err := manager.evaluate(now.Add(2 * time.Minute)); err != nil { t.Fatal(err) } afterTransaction, _ := loadAnchor(manager.anchorPath()) if afterTransaction.Fingerprint == initial.Fingerprint { t.Fatal("transaction-correlated change did not refresh anchor") } }