eleven / ocho Public

Clone
git clone https://amadan.net/eleven/ocho

Plain git — no account needed to clone.

Download

Download this file

1package main
2
3import (
4 "encoding/json"
5 "net/http"
6 "strings"
7 "testing"
8)
9
10// Block = no new contact, server-enforced: no fresh DM either way, no group
11// adds either way, any existing DM closes at once, pushes from the blocked
12// stop, and unblocking restores possibility (not the conversation).
13func TestBlock(t *testing.T) {
14 ts, srv := newTestServer(t)
15
16 tokA, uidA, _ := joinedMember(t, srv, "Alice")
17 tokB, uidB, _ := joinedMember(t, srv, "Bob")
18
19 // An existing DM, open and working.
20 code, out := modPost(t, ts.URL+"/api/threads", dmBody(tokA, uidA, uidB))
21 if code != 200 {
22 t.Fatalf("create dm: %d", code)
23 }
24 tid := out["thread_id"].(string)
25
26 // Alice blocks Bob.
27 if code, _ := modPost(t, ts.URL+"/api/blocks", `{"token":"`+tokA+`","user_id":"`+uidB+`"}`); code != 200 {
28 t.Fatal("block should succeed")
29 }
30 // Her list shows him.
31 resp, err := http.Get(ts.URL + "/api/blocks?token=" + tokA)
32 if err != nil {
33 t.Fatal(err)
34 }
35 var got struct {
36 Blocked []string `json:"blocked"`
37 }
38 _ = json.NewDecoder(resp.Body).Decode(&got)
39 resp.Body.Close()
40 if len(got.Blocked) != 1 || got.Blocked[0] != uidB {
41 t.Fatalf("blocks list: %v", got.Blocked)
42 }
43
44 // The DM closed with the block.
45 if closed, by := srv.db.dmClosed(tid); !closed || by != uidA {
46 t.Fatalf("block should close the DM, got closed=%v by=%q", closed, by)
47 }
48 if code, _ := modPost(t, ts.URL+"/api/messages?token="+tokB, `{"thread_id":"`+tid+`","blob":"hi"}`); code != 403 {
49 t.Fatal("messages into the blocked DM should bounce")
50 }
51 // Bob can't reopen it, and can't open a "new" DM either (dedupe returns
52 // the existing one, which stays closed — so contact stays cut).
53 if code, _ := modPost(t, ts.URL+"/api/threads/"+tid+"/reopen-dm", `{"token":"`+tokB+`"}`); code != 403 {
54 t.Fatal("blocked party reopening should be 403")
55 }
56 // Alice can't reopen through her own block either — unblock first.
57 if code, _ := modPost(t, ts.URL+"/api/threads/"+tid+"/reopen-dm", `{"token":"`+tokA+`"}`); code != 403 {
58 t.Fatal("blocker reopening without unblocking should be 403")
59 }
60
61 // Neither can add the other to a group.
62 gidA, err := srv.db.createThread("group", "", []ThreadMemberKey{{UserID: uidA, Wrapped: "w"}})
63 if err != nil {
64 t.Fatal(err)
65 }
66 if code, _ := modPost(t, ts.URL+"/api/threads/"+gidA+"/members", `{"token":"`+tokA+`","user_id":"`+uidB+`","wrapped":"w"}`); code != 403 {
67 t.Fatal("blocker adding blocked to a group should be 403")
68 }
69 gidB, err := srv.db.createThread("group", "", []ThreadMemberKey{{UserID: uidB, Wrapped: "w"}})
70 if err != nil {
71 t.Fatal(err)
72 }
73 if code, _ := modPost(t, ts.URL+"/api/threads/"+gidB+"/members", `{"token":"`+tokB+`","user_id":"`+uidA+`","wrapped":"w"}`); code != 403 {
74 t.Fatal("blocked adding blocker to a group should be 403")
75 }
76
77 // Push fan-out: Alice (who blocks Bob) drops out of Bob's recipient sets.
78 set := map[string]bool{uidA: true}
79 srv.dropBlockingRecipients(uidB, set)
80 if set[uidA] {
81 t.Fatal("a blocker should never be pushed the blocked member's messages")
82 }
83
84 // Admin still passes (a reported member can't dodge a warning by blocking).
85 tokAdm, uidAdm, _ := joinedMember(t, srv, "Root")
86 if err := srv.db.setUserRole(uidAdm, "admin"); err != nil {
87 t.Fatal(err)
88 }
89 if code, _ := modPost(t, ts.URL+"/api/blocks", `{"token":"`+tokB+`","user_id":"`+uidAdm+`"}`); code != 200 {
90 t.Fatal("blocking the admin records fine")
91 }
92 if code, _ := modPost(t, ts.URL+"/api/threads", dmBody(tokAdm, uidAdm, uidB)); code != 200 {
93 t.Fatal("the admin's DM passes a block (formal notices must land)")
94 }
95
96 // Unblock: contact becomes possible again, but nothing reopens by itself.
97 if code2, _ := modDelete(t, ts.URL+"/api/blocks/"+uidB, `{"token":"`+tokA+`"}`); code2 != 200 {
98 t.Fatal("unblock should succeed")
99 }
100 if closed, _ := srv.db.dmClosed(tid); !closed {
101 t.Fatal("unblocking must not silently reopen the conversation")
102 }
103 if code, _ := modPost(t, ts.URL+"/api/threads/"+tid+"/reopen-dm", `{"token":"`+tokA+`"}`); code != 200 {
104 t.Fatal("after unblocking, reopening is a normal ask again")
105 }
106}
107
108func modDelete(t *testing.T, url, body string) (int, map[string]any) {
109 t.Helper()
110 req, err := http.NewRequest("DELETE", url, strings.NewReader(body))
111 if err != nil {
112 t.Fatal(err)
113 }
114 req.Header.Set("Content-Type", "application/json")
115 resp, err := http.DefaultClient.Do(req)
116 if err != nil {
117 t.Fatalf("delete %s: %v", url, err)
118 }
119 defer resp.Body.Close()
120 out := map[string]any{}
121 _ = json.NewDecoder(resp.Body).Decode(&out)
122 return resp.StatusCode, out
123}
124