Every alert was titled "brain-capture audit degraded", including the symlink tripwire's and governance-edit alerts. NtfyNotifier.WithTitle returns a titled copy (nil stays nil); buildNotifier(getenv, title) is called per source: capture audit (unchanged title), "brain symlink tripwire", "brain governance edit". Tests assert the Title header. Fixes #163. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
93 lines
3.2 KiB
Go
93 lines
3.2 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"syscall"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// hyperguild#157: the server refuses to start while a symlink exists in the
|
|
// brain dir, or while any part of it cannot be scanned (which could hide
|
|
// one); a missing brain dir is the only thing it lets pass.
|
|
func TestStartupSymlinkCheckRefusesABrainWithASymlink(t *testing.T) {
|
|
brain := t.TempDir()
|
|
require.NoError(t, os.Symlink(t.TempDir(), filepath.Join(brain, "planted")))
|
|
err := startupSymlinkCheck(brain)
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "planted")
|
|
}
|
|
|
|
func TestStartupSymlinkCheckRefusesABrainWithAnUnreadableDir(t *testing.T) {
|
|
if os.Geteuid() == 0 {
|
|
t.Skip("root reads mode-000 directories")
|
|
}
|
|
brain := t.TempDir()
|
|
locked := filepath.Join(brain, "aaa")
|
|
require.NoError(t, os.MkdirAll(locked, 0o755))
|
|
require.NoError(t, os.Chmod(locked, 0o000))
|
|
t.Cleanup(func() { _ = os.Chmod(locked, 0o755) })
|
|
err := startupSymlinkCheck(brain)
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "aaa")
|
|
}
|
|
|
|
func TestStartupSymlinkCheckPassesACleanOrMissingBrain(t *testing.T) {
|
|
brain := t.TempDir()
|
|
require.NoError(t, os.WriteFile(filepath.Join(brain, "n.md"), []byte("x"), 0o644))
|
|
assert.NoError(t, startupSymlinkCheck(brain))
|
|
assert.NoError(t, startupSymlinkCheck(filepath.Join(brain, "missing")))
|
|
}
|
|
|
|
func TestSymlinkScanIntervalFallsBackOnNonPositive(t *testing.T) {
|
|
for _, v := range []string{"0", "-5", "", "junk"} {
|
|
got := symlinkScanInterval(func(string) string { return v })
|
|
assert.Positive(t, got, "env %q", v)
|
|
}
|
|
}
|
|
|
|
func TestAlertingWarningsNamesTheSymlinkTripwire(t *testing.T) {
|
|
w := strings.Join(alertingWarnings(func(string) string { return "" }), " ")
|
|
assert.Contains(t, w, "symlink tripwire")
|
|
}
|
|
|
|
func TestStartupSymlinkCheckRefusesAnUnlistableBrainRoot(t *testing.T) {
|
|
if os.Geteuid() == 0 {
|
|
t.Skip("root lists mode-0100 directories")
|
|
}
|
|
brain := t.TempDir()
|
|
require.NoError(t, os.Chmod(brain, 0o100))
|
|
t.Cleanup(func() { _ = os.Chmod(brain, 0o755) })
|
|
assert.Error(t, startupSymlinkCheck(brain))
|
|
}
|
|
|
|
// hyperguild#179: a FIFO in the brain is refused at startup with a clear
|
|
// message, instead of hanging or exiting later in a reader.
|
|
func TestStartupSymlinkCheckRefusesABrainWithAFIFO(t *testing.T) {
|
|
brain := t.TempDir()
|
|
require.NoError(t, syscall.Mkfifo(filepath.Join(brain, "classification.yaml"), 0o644))
|
|
err := startupSymlinkCheck(brain)
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "classification.yaml")
|
|
}
|
|
|
|
// hyperguild#163: each alert source gets its own ntfy title.
|
|
func TestBuildNotifierUsesTheGivenTitle(t *testing.T) {
|
|
var got string
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { got = r.Header.Get("Title") }))
|
|
defer srv.Close()
|
|
env := map[string]string{"BRAIN_NTFY_URL": srv.URL}
|
|
n := buildNotifier(func(k string) string { return env[k] }, "brain symlink tripwire")
|
|
require.NotNil(t, n)
|
|
require.NoError(t, n.Notify(context.Background(), "m"))
|
|
assert.Equal(t, "brain symlink tripwire", got)
|
|
assert.Nil(t, buildNotifier(func(string) string { return "" }, "x"), "no ntfy URL, no notifier")
|
|
}
|