tinyrelayfile
tinyrelay/internal/daemon/event_pipeline_test.go/

internal/daemon/event_pipeline_test.go raw
1package daemon
2
3import (
4 "context"
5 "encoding/json"
6 "testing"
7 "time"
8
9 "github.com/FelineStateMachine/tinyrelay/internal/relay"
10 "github.com/FelineStateMachine/tinyrelay/internal/replication"
11 "github.com/FelineStateMachine/tinyrelay/internal/storage"
12 "github.com/FelineStateMachine/tinyrelay/internal/work"
13)
14
15func TestOptionalFollowupFailurePreservesEventAndRecovery(t *testing.T) {
16 for _, source := range []string{"client", "import"} {
17 t.Run(source, func(t *testing.T) {
18 tenant, remote, _ := viewTenant(t, []int{30023}, nil)
19 ctx := context.Background()
20 e := signedEvent(t, testOwnerSecret, 30023, time.Now().Unix(), [][]string{{"d", "recover-view"}}, "```mermaid\ngraph LR; A-->B\n```")
21 if _, err := tenant.store.DB().ExecContext(ctx, `CREATE TRIGGER reject_view_work BEFORE INSERT ON work_intents WHEN NEW.kind='view-transform' BEGIN SELECT RAISE(ABORT,'view work unavailable'); END`); err != nil {
22 t.Fatal(err)
23 }
24 if source == "import" {
25 if err := tenant.commitImported(ctx, e, replication.OriginImport); err != nil {
26 t.Fatal(err)
27 }
28 } else if _, err := tenant.Publish(ctx, e, relay.Session{PubKeys: []string{e.PubKey}}); err != nil {
29 t.Fatal(err)
30 }
31 var stored int
32 if err := tenant.store.DB().QueryRowContext(ctx, `SELECT count(*) FROM events WHERE id=?`, e.ID).Scan(&stored); err != nil {
33 t.Fatal(err)
34 }
35 if stored != 1 {
36 t.Fatal("optional planning failure rejected valid event")
37 }
38 job := followupJob(t, tenant, e.ID)
39 if remote.count() != 0 {
40 t.Fatal("planning contacted the remote transform")
41 }
42 if _, err := tenant.store.DB().ExecContext(ctx, `DROP TRIGGER reject_view_work`); err != nil {
43 t.Fatal(err)
44 }
45 intent := storage.Intent{Kind: job.Kind, EventID: job.EventID, Target: job.Target, Payload: job.Payload}
46 tenant.followups.try(ctx, &intent)
47 if intents := pendingViewIntents(t, tenant, "diagrams", e.ID); len(intents) != 1 {
48 t.Fatalf("recovery queued %d view intents, want one", len(intents))
49 }
50 var state string
51 if err := tenant.store.DB().QueryRowContext(ctx, `SELECT state FROM work_intents WHERE id=?`, job.ID).Scan(&state); err != nil {
52 t.Fatal(err)
53 }
54 if state != "completed" {
55 t.Fatalf("recovered planning state=%s", state)
56 }
57 if _, err := tenant.store.DB().ExecContext(ctx, `UPDATE work_intents SET state='completed' WHERE kind=? AND event_id=?`, viewTransform, e.ID); err != nil {
58 t.Fatal(err)
59 }
60 if err := tenant.followups.handle(ctx, job); err != nil {
61 t.Fatal(err)
62 }
63 if intents := pendingViewIntents(t, tenant, "diagrams", e.ID); len(intents) != 0 {
64 t.Fatal("recovery replayed already completed transform work")
65 }
66 })
67 }
68}
69
70func TestFollowupPlanningSkipsRemovedSourcesAndNewTargets(t *testing.T) {
71 tenant, _, _ := viewTenant(t, []int{30023}, nil)
72 ctx := context.Background()
73 e := signedEvent(t, testOwnerSecret, 30023, time.Now().Unix(), [][]string{{"d", "captured-targets"}}, "```mermaid\ngraph LR; A-->B\n```")
74 intent := tenant.followups.prepare(ctx, e, time.Now().Unix(), false)
75 if intent == nil {
76 t.Fatal("matching view did not create planning intent")
77 }
78 if _, err := tenant.store.Save(ctx, e, storage.SaveOptions{Now: time.Now().Unix(), Intents: []storage.Intent{*intent}}); err != nil {
79 t.Fatal(err)
80 }
81 addView(t, tenant, map[string]any{"name": "later", "kinds": []int{30023}, "transform": "https://later.example/render", "languages": []string{"mermaid"}})
82 job := followupJob(t, tenant, e.ID)
83 if err := tenant.followups.handle(ctx, job); err != nil {
84 t.Fatal(err)
85 }
86 if intents := pendingViewIntents(t, tenant, "later", e.ID); len(intents) != 0 {
87 t.Fatal("new view received an older captured event")
88 }
89 if _, err := tenant.store.DB().ExecContext(ctx, `DELETE FROM work_intents WHERE kind=? AND event_id=?`, viewTransform, e.ID); err != nil {
90 t.Fatal(err)
91 }
92 if _, err := tenant.store.DB().ExecContext(ctx, `DELETE FROM events WHERE id=?`, e.ID); err != nil {
93 t.Fatal(err)
94 }
95 if err := tenant.followups.handle(ctx, job); err != nil {
96 t.Fatal(err)
97 }
98 if intents := pendingViewIntents(t, tenant, "diagrams", e.ID); len(intents) != 0 {
99 t.Fatal("removed source was replanned")
100 }
101 var payload eventFollowupPayload
102 if err := json.Unmarshal([]byte(job.Payload), &payload); err != nil {
103 t.Fatal(err)
104 }
105 payload.AcceptedAt = time.Now().Add(-followupMaxAge - time.Minute).Unix()
106 raw, err := json.Marshal(payload)
107 if err != nil {
108 t.Fatal(err)
109 }
110 job.Payload = string(raw)
111 if err := tenant.followups.handle(ctx, job); err == nil {
112 t.Fatal("expired optional planning was not stopped")
113 }
114}
115
116func followupJob(t *testing.T, tenant *Tenant, eventID string) work.Intent {
117 t.Helper()
118 job := work.Intent{Kind: eventFollowupKind, EventID: eventID, Attempts: 1}
119 if err := tenant.store.DB().QueryRow(`SELECT id,target,payload FROM work_intents WHERE kind=? AND event_id=?`, eventFollowupKind, eventID).Scan(&job.ID, &job.Target, &job.Payload); err != nil {
120 t.Fatal(err)
121 }
122 return job
123}

About

ownerc01503e8035f
visibilitypublic
headd730679639bf

Maintainers

  • c01503e8035f owner
  • ba1b5beed5b5 agent hermes

Clone

  • https://012.run/npub1cq2s86qrtadf36eqq3epm38wmhdynrrcwxf84eldk3f94dkv7ttsrulrz5/tinyrelay.git

Refs