microblog_test.gno
3.95 Kb · 160 lines
1package microblog
2
3import (
4 "strings"
5 "testing"
6
7 "gno.land/p/nt/testutils/v0"
8 "gno.land/p/nt/uassert/v0"
9)
10
11// resetWall clears package state between tests (globals persist in-process,
12// and subtests do not reset them — do it explicitly).
13func resetWall() { posts = nil }
14
15// asUser must be inlined into each test: SetRealm affects the calling frame
16// only. We therefore call it directly in the test bodies below.
17
18func TestPostAndCount(cur realm, t *testing.T) {
19 resetWall()
20
21 alice := testutils.TestAddress("alice")
22 testing.SetRealm(testing.NewUserRealm(alice))
23 Post(cross(cur), "hello wall")
24
25 if got := Count(); got != 1 {
26 t.Fatalf("Count() = %d, want 1", got)
27 }
28 if posts[0].Msg != "hello wall" {
29 t.Errorf("Msg = %q, want %q", posts[0].Msg, "hello wall")
30 }
31 if posts[0].Author != alice.String() {
32 t.Errorf("Author = %q, want %q", posts[0].Author, alice.String())
33 }
34}
35
36func TestPostTooLongPanics(cur realm, t *testing.T) {
37 resetWall()
38 testing.SetRealm(testing.NewUserRealm(testutils.TestAddress("bob")))
39
40 // Aborts from a cross(cur) call cannot be caught by a plain recover();
41 // assert on the abort message instead.
42 uassert.AbortsWithMessage(t, cur, "microblog: message too long (max 280 bytes)", func() {
43 Post(cross(cur), strings.Repeat("x", maxMsgLen+1))
44 })
45}
46
47func TestPostEmptyPanics(cur realm, t *testing.T) {
48 resetWall()
49 testing.SetRealm(testing.NewUserRealm(testutils.TestAddress("bob")))
50
51 // Aborts from a cross(cur) call cannot be caught by a plain recover();
52 // assert on the abort message instead.
53 uassert.AbortsWithMessage(t, cur, "microblog: empty message", func() {
54 Post(cross(cur), "")
55 })
56}
57
58func TestPostMaxLenOK(cur realm, t *testing.T) {
59 resetWall()
60 testing.SetRealm(testing.NewUserRealm(testutils.TestAddress("carol")))
61
62 // Exactly maxMsgLen bytes must be accepted.
63 Post(cross(cur), strings.Repeat("y", maxMsgLen))
64 if got := Count(); got != 1 {
65 t.Fatalf("Count() = %d, want 1 for exactly-max message", got)
66 }
67}
68
69func TestRenderEmpty(t *testing.T) {
70 resetWall()
71 out := Render("")
72 if !strings.Contains(out, "No posts yet") {
73 t.Errorf("empty Render should mention no posts, got:\n%s", out)
74 }
75}
76
77func TestRenderNewestFirstAndPagination(cur realm, t *testing.T) {
78 resetWall()
79 testing.SetRealm(testing.NewUserRealm(testutils.TestAddress("dave")))
80
81 // 25 posts -> 2 pages (20 + 5).
82 for i := 0; i < 25; i++ {
83 Post(cross(cur), "msg"+itoa(i))
84 }
85
86 if got := Count(); got != 25 {
87 t.Fatalf("Count() = %d, want 25", got)
88 }
89
90 page1 := Render("")
91 // Newest (msg24) must appear before older (msg5) on page 1.
92 i24 := strings.Index(page1, "msg24")
93 i5 := strings.Index(page1, "msg5")
94 if i24 < 0 || i5 < 0 {
95 t.Fatalf("page 1 missing expected posts: msg24=%d msg5=%d", i24, i5)
96 }
97 if i24 > i5 {
98 t.Errorf("page 1 not newest-first: msg24 at %d should precede msg5 at %d", i24, i5)
99 }
100 if !strings.Contains(page1, "page 1 of 2") {
101 t.Errorf("page 1 header wrong, got:\n%s", page1)
102 }
103
104 // Page 2 via query form must contain the oldest post.
105 page2 := Render("?page=2")
106 if !strings.Contains(page2, "msg0") {
107 t.Errorf("page 2 should contain oldest msg0, got:\n%s", page2)
108 }
109 if !strings.Contains(page2, "page 2 of 2") {
110 t.Errorf("page 2 header wrong, got:\n%s", page2)
111 }
112
113 // Path form "/2" should equal query form "?page=2".
114 if Render("/2") != page2 {
115 t.Errorf("path form /2 differs from query form ?page=2")
116 }
117}
118
119func TestParsePage(t *testing.T) {
120 cases := []struct {
121 in string
122 want int
123 }{
124 {"", 1},
125 {"?page=3", 3},
126 {"?p=4", 4},
127 {"/2", 2},
128 {"5", 5},
129 {"?page=abc", 1},
130 {"garbage", 1},
131 }
132 for _, c := range cases {
133 if got := parsePage(c.in); got != c.want {
134 t.Errorf("parsePage(%q) = %d, want %d", c.in, got, c.want)
135 }
136 }
137}
138
139// itoa is a tiny local int->string helper for building test messages.
140func itoa(n int) string {
141 if n == 0 {
142 return "0"
143 }
144 neg := n < 0
145 if neg {
146 n = -n
147 }
148 var buf [20]byte
149 i := len(buf)
150 for n > 0 {
151 i--
152 buf[i] = byte('0' + n%10)
153 n /= 10
154 }
155 if neg {
156 i--
157 buf[i] = '-'
158 }
159 return string(buf[i:])
160}