| 1 | package configport |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | "strings" |
| 6 | "testing" |
| 7 | |
| 8 | "github.com/FelineStateMachine/tinyrelay/internal/community" |
| 9 | "github.com/FelineStateMachine/tinyrelay/internal/policy" |
| 10 | "github.com/FelineStateMachine/tinyrelay/internal/storage" |
| 11 | ) |
| 12 | |
| 13 | func TestParsePlanOmittedAndEmptySections(t *testing.T) { |
| 14 | ctx := context.Background() |
| 15 | st, err := storage.Open(ctx, t.TempDir()+"/relay.db" ) |
| 16 | if err != nil { |
| 17 | t.Fatal(err) |
| 18 | } |
| 19 | defer st.Close() |
| 20 | owner := strings.Repeat("a" , 64) |
| 21 | c, err := community.New(ctx, st, owner) |
| 22 | if err != nil { |
| 23 | t.Fatal(err) |
| 24 | } |
| 25 | cur := policy.Defaults(owner) |
| 26 | svc := New(ConfigStore{Store: st, Community: c, Policy: func() policy.Policy { return cur }}) |
| 27 | doc, err := Parse([]byte(`{"format":"bind.ws/relay-config/2","policy":{"reads":"members"}}` )) |
| 28 | if err != nil { |
| 29 | t.Fatal(err) |
| 30 | } |
| 31 | plan, err := svc.Plan(ctx, doc) |
| 32 | if err != nil { |
| 33 | t.Fatal(err) |
| 34 | } |
| 35 | if len(plan.Policy) == 0 { |
| 36 | t.Fatal("expected policy diff" ) |
| 37 | } |
| 38 | if len(plan.Members) != 0 { |
| 39 | t.Fatal("omitted members changed plan" ) |
| 40 | } |
| 41 | doc, err = Parse([]byte(`{"format":"bind.ws/relay-config/2","members":[]}` )) |
| 42 | if err != nil { |
| 43 | t.Fatal(err) |
| 44 | } |
| 45 | plan, err = svc.Plan(ctx, doc) |
| 46 | if err != nil { |
| 47 | t.Fatal(err) |
| 48 | } |
| 49 | if !plan.MembersCleared { |
| 50 | t.Fatal("empty members must clear" ) |
| 51 | } |
| 52 | } |
| 53 | |
| 54 | func TestApplyDryRunAndEconomyMigrationWarning(t *testing.T) { |
| 55 | ctx := context.Background() |
| 56 | st, err := storage.Open(ctx, t.TempDir()+"/relay.db" ) |
| 57 | if err != nil { |
| 58 | t.Fatal(err) |
| 59 | } |
| 60 | defer st.Close() |
| 61 | owner := strings.Repeat("a" , 64) |
| 62 | _, err = community.New(ctx, st, owner) |
| 63 | if err != nil { |
| 64 | t.Fatal(err) |
| 65 | } |
| 66 | cur := policy.Defaults(owner) |
| 67 | changed := false |
| 68 | svc := New(ConfigStore{Store: st, Policy: func() policy.Policy { return cur }, OnApplied: func(policy.Policy) { changed = true }}) |
| 69 | doc, err := Parse([]byte(`{"format":"bind.ws/relay-config/2","policy":{"fuel":10,"reads":"auth"}}` )) |
| 70 | if err != nil { |
| 71 | t.Fatal(err) |
| 72 | } |
| 73 | if len(doc.Warnings) == 0 { |
| 74 | t.Fatal("expected removed economy warning" ) |
| 75 | } |
| 76 | if _, err := svc.Apply(ctx, doc, true); err != nil { |
| 77 | t.Fatal(err) |
| 78 | } |
| 79 | if changed { |
| 80 | t.Fatal("dry run mutated policy" ) |
| 81 | } |
| 82 | } |
| 83 | |
| 84 | func TestApplyAtomicAcrossPolicyMetadataAndJobs(t *testing.T) { |
| 85 | ctx := context.Background() |
| 86 | st, err := storage.Open(ctx, t.TempDir()+"/relay.db" ) |
| 87 | if err != nil { |
| 88 | t.Fatal(err) |
| 89 | } |
| 90 | defer st.Close() |
| 91 | owner := strings.Repeat("a" , 64) |
| 92 | _, err = community.New(ctx, st, owner) |
| 93 | if err != nil { |
| 94 | t.Fatal(err) |
| 95 | } |
| 96 | cur := policy.Defaults(owner) |
| 97 | if err := st.PutSetting(ctx, "policy" , cur); err != nil { |
| 98 | t.Fatal(err) |
| 99 | } |
| 100 | svc := New(ConfigStore{Store: st, Policy: func() policy.Policy { return cur }}) |
| 101 | if _, err := st.DB().Exec(`INSERT INTO config_jobs(id,recipe) VALUES('old','{"source":"old"}')` ); err != nil { |
| 102 | t.Fatal(err) |
| 103 | } |
| 104 | if _, err := st.DB().Exec(`INSERT INTO community_members(pubkey,name,note,role,created_at) VALUES(?,?,?,?,?)` , strings.Repeat("b" , 64), "old" , "" , "member" , 1); err != nil { |
| 105 | t.Fatal(err) |
| 106 | } |
| 107 | doc, err := Parse([]byte(`{"format":"bind.ws/relay-config/2","policy":{"reads":"auth"},"members":[{"pubkey":"cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc","role":"member"}],"kinds":{"allow":[1],"block":[1]},"jobs":[{"id":"new","source":"new"}]}` )) |
| 108 | if err != nil { |
| 109 | t.Fatal(err) |
| 110 | } |
| 111 | if _, err := svc.Apply(ctx, doc, false); err == nil { |
| 112 | t.Fatal("expected duplicate kind rule failure" ) |
| 113 | } |
| 114 | var got policy.Policy |
| 115 | if err := st.GetSetting(ctx, "policy" , &got); err != nil { |
| 116 | t.Fatal(err) |
| 117 | } |
| 118 | if got.Reads != cur.Reads { |
| 119 | t.Fatalf("policy committed despite rollback: %q" , got.Reads) |
| 120 | } |
| 121 | var jobs, members int |
| 122 | if err := st.DB().QueryRow(`SELECT count(*) FROM config_jobs WHERE id='old'` ).Scan(&jobs); err != nil { |
| 123 | t.Fatal(err) |
| 124 | } |
| 125 | if err := st.DB().QueryRow(`SELECT count(*) FROM community_members WHERE pubkey=?` , strings.Repeat("b" , 64)).Scan(&members); err != nil { |
| 126 | t.Fatal(err) |
| 127 | } |
| 128 | if jobs != 1 || members != 1 { |
| 129 | t.Fatalf("metadata committed despite rollback: jobs=%d members=%d" , jobs, members) |
| 130 | } |
| 131 | } |
| 132 | |
| 133 | func TestApplyJobsFeedsReplicationQueue(t *testing.T) { |
| 134 | ctx := context.Background() |
| 135 | st, err := storage.Open(ctx, t.TempDir()+"/relay.db" ) |
| 136 | if err != nil { |
| 137 | t.Fatal(err) |
| 138 | } |
| 139 | defer st.Close() |
| 140 | owner := strings.Repeat("a" , 64) |
| 141 | if _, err := community.New(ctx, st, owner); err != nil { |
| 142 | t.Fatal(err) |
| 143 | } |
| 144 | cur := policy.Defaults(owner) |
| 145 | svc := New(ConfigStore{Store: st, Community: nil, Policy: func() policy.Policy { return cur }}) |
| 146 | doc, err := Parse([]byte(`{"format":"bind.ws/relay-config/2","jobs":[{"id":"pull-1","kind":"pull","relays":["wss://relay.example"]}]}` )) |
| 147 | if err != nil { |
| 148 | t.Fatal(err) |
| 149 | } |
| 150 | if _, err := svc.Apply(ctx, doc, false); err != nil { |
| 151 | t.Fatal(err) |
| 152 | } |
| 153 | var payload string |
| 154 | if err := st.DB().QueryRow(`SELECT payload FROM replication_jobs WHERE id='pull-1'` ).Scan(&payload); err != nil { |
| 155 | t.Fatal(err) |
| 156 | } |
| 157 | if !strings.Contains(payload, `"kind":"pull"` ) { |
| 158 | t.Fatalf("replication payload: %s" , payload) |
| 159 | } |
| 160 | } |