events: verify, normalize, append-only store with provider-id dedup
Signature verification (gitea hex HMAC-SHA256 over the raw body via constant-time hmac.Equal; redmine/discourse constant-time shared-secret headers) with one generic ErrUnverified so rejects give attackers no oracle. Tolerant normalization of the known Redmine/Discourse/Gitea payload variants into one Event record (canonical subject ids, actor, title, repo, sha256 payload digest) plus the DESIGN action mapping (dispatch_turn / respond_turn / pipeline_step / ignore). Store: JSONL under state dir, 0600, dedup keyed on provider event id (delivery header, payload-digest fallback), index rebuilt at startup so replays across restarts still dedup; torn tail lines skipped, not fatal.
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
package events
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestStoreAppendAndDedup(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
s, err := OpenStore(dir)
|
||||
if err != nil {
|
||||
t.Fatalf("OpenStore: %v", err)
|
||||
}
|
||||
base := Event{
|
||||
Source: SourceGitea, Kind: "pr_approved", Action: ActionPipelineStep,
|
||||
ProviderID: "d-1", PayloadDigest: strings.Repeat("a", 64), ReceivedAt: testNow,
|
||||
}
|
||||
|
||||
if stored, err := s.Append(base); err != nil || !stored {
|
||||
t.Fatalf("first append = stored=%v err=%v", stored, err)
|
||||
}
|
||||
if stored, err := s.Append(base); err != nil || stored {
|
||||
t.Fatalf("replay append = stored=%v err=%v (want dup)", stored, err)
|
||||
}
|
||||
sameIDOtherSource := base
|
||||
sameIDOtherSource.Source = SourceRedmine
|
||||
if stored, err := s.Append(sameIDOtherSource); err != nil || !stored {
|
||||
t.Fatalf("same provider id, other source = stored=%v err=%v (want stored)", stored, err)
|
||||
}
|
||||
if s.Len() != 2 {
|
||||
t.Errorf("Len = %d, want 2", s.Len())
|
||||
}
|
||||
s.Close()
|
||||
|
||||
data, err := os.ReadFile(filepath.Join(dir, "events.jsonl"))
|
||||
if err != nil {
|
||||
t.Fatalf("read log: %v", err)
|
||||
}
|
||||
lines := strings.Split(strings.TrimSpace(string(data)), "\n")
|
||||
if len(lines) != 2 {
|
||||
t.Fatalf("log has %d lines, want 2:\n%s", len(lines), data)
|
||||
}
|
||||
if !strings.Contains(lines[0], `"provider_id":"d-1"`) {
|
||||
t.Errorf("line 1 missing provider id: %s", lines[0])
|
||||
}
|
||||
if !strings.Contains(lines[0], `"source":"gitea"`) {
|
||||
t.Errorf("line 1 wrong source: %s", lines[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestStoreDedupSurvivesRestart(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
s, err := OpenStore(dir)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ev := Event{Source: SourceDiscourse, Kind: "post_created", ProviderID: "ev-9", ReceivedAt: time.Now()}
|
||||
if stored, _ := s.Append(ev); !stored {
|
||||
t.Fatal("first append must store")
|
||||
}
|
||||
s.Close()
|
||||
|
||||
s2, err := OpenStore(dir)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer s2.Close()
|
||||
if stored, _ := s2.Append(ev); stored {
|
||||
t.Fatal("duplicate after restart must not store again")
|
||||
}
|
||||
if s2.Len() != 1 {
|
||||
t.Errorf("Len after restart = %d, want 1", s2.Len())
|
||||
}
|
||||
}
|
||||
|
||||
func TestStoreSkipsTornTailLine(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
path := filepath.Join(dir, "events.jsonl")
|
||||
good := `{"source":"gitea","provider_id":"d-1","kind":"push"}` + "\n"
|
||||
torn := `{"source":"gitea","provider_id":"d-2","ki`
|
||||
if err := os.WriteFile(path, []byte(good+torn), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
s, err := OpenStore(dir)
|
||||
if err != nil {
|
||||
t.Fatalf("OpenStore with torn tail: %v", err)
|
||||
}
|
||||
defer s.Close()
|
||||
if s.Len() != 1 {
|
||||
t.Errorf("Len = %d, want 1 (torn line skipped)", s.Len())
|
||||
}
|
||||
if stored, _ := s.Append(Event{Source: SourceGitea, ProviderID: "d-2", ReceivedAt: time.Now()}); !stored {
|
||||
t.Errorf("event from torn line must be appendable again")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStorePermissions(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
s, err := OpenStore(dir)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
s.Close()
|
||||
fi, err := os.Stat(filepath.Join(dir, "events.jsonl"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if fi.Mode().Perm() != 0o600 {
|
||||
t.Errorf("events.jsonl mode = %v, want 0600", fi.Mode().Perm())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user