Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

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}