Files
hyperguild/ingestion/cmd/server/main_test.go
T
mathias 67c3039448
CI / Lint / Test / Vet (pull_request) Successful in 36s
CI / Mirror to GitHub (pull_request) Skipped
feat(server): say at startup when alerting is unconfigured (#107)
buildNotifier returns a nil interface when BRAIN_NTFY_URL is unset, and
every consumer skips a nil notifier silently. That is right for an
unconfigured homelab and wrong for an alert whose entire value is that
someone finds out: "configured and working" and "never configured" were
byte-identical in behaviour — no error, no warning, only a message that
never arrives, which nobody notices because nobody is waiting for an alert
they have never seen.

Two alerts were dark in production for exactly that reason (#100): the
governance-rewrite alert from #97, and the audit-degradation alert that
reports the audit trail is down. Both were found by firing a real event
and watching nothing happen, not by reading anything.

WARN rather than fatal, deliberately. The brain rule
`empty-credential-must-refuse-to-start-not-switch-modes` says an empty
credential must be a startup error rather than a silent mode switch — but
that is about a control being removed. Here observability degrades, no gate
is bypassed, and refusing to boot would break the unconfigured-homelab case
the nil notifier exists to support. What that rule demands and this
provides is that the mode switch stops being silent.

buildAuditSink already announces its own fallback ("capture audit: slog
sink (BRAIN_LOKI_URL unset)"), so only the notifier needed this.

alertingWarnings is pure and env-injected: the reason this gap survived is
that nothing about it was observable, so the fix is tested rather than
asserted.

Refs #107, #100, #97.
2026-09-23 14:58:27 +02:00

79 lines
2.2 KiB
Go

// ingestion/cmd/server/main_test.go
package main
import (
"context"
"log/slog"
"os"
"path/filepath"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/mathiasbq/hyperguild/ingestion/internal/claudewatcher"
)
func TestClaudeSink_IngestWritesToNonIndexedArchiveNotWiki(t *testing.T) {
dir := t.TempDir()
sink := &claudeSink{brainDir: dir, logger: slog.New(slog.NewTextHandler(os.Stderr, nil))}
err := sink.Ingest(context.Background(), claudewatcher.Batch{
Host: "koala",
FilePath: "/host-home-claude/projects/-home-mathias-dev/abc.jsonl",
SessionID: "abc",
ProjectID: "-home-mathias-dev",
Turns: []claudewatcher.Turn{
{Type: "assistant", Content: "did a thing"},
},
})
require.NoError(t, err)
got, err := os.ReadFile(filepath.Join(dir, "archive", "claude-sessions", "koala", "session-koala-abc.md"))
require.NoError(t, err, "raw session dump must land in the non-indexed archive")
assert.Contains(t, string(got), "did a thing")
_, err = os.Stat(filepath.Join(dir, "wiki", "claude-sessions"))
assert.True(t, os.IsNotExist(err), "raw transcripts must never land under wiki/ (ai-sessions#10 — BM25 pollution)")
}
// --- #107: an unconfigured alert must announce itself ----------------------
func TestAlertingWarnings(t *testing.T) {
tests := []struct {
name string
env map[string]string
want []string // substrings that must appear across the warnings
none bool // true = expect no warnings at all
}{
{
// The failure this exists to prevent: two alerts dark in
// production, discovered only by firing a real event and watching
// nothing arrive (#100).
name: "notifier unconfigured names what will not fire",
env: map[string]string{},
want: []string{"BRAIN_NTFY_URL", "brain_edit_root", "audit"},
},
{
name: "configured notifier is silent",
env: map[string]string{"BRAIN_NTFY_URL": "http://ntfy/brain-alerts"},
none: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := alertingWarnings(func(k string) string { return tt.env[k] })
if tt.none {
assert.Empty(t, got)
return
}
joined := strings.Join(got, " ")
for _, w := range tt.want {
assert.Contains(t, joined, w)
}
})
}
}