clone

a self-hosted multitenant nostr relay with sites, file storage, git hosting, and inbox and outbox delivery

main
tinyrelay / internal / daemon / event_pipeline_test.go
event_pipeline_test.go 4.9K
1package daemon23import (4 "context"5 "encoding/json"6 "testing"7 "time"89 "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)1415func 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 int32 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 string51 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}6970func 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 eventFollowupPayload102 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}115116func 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 job123}