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

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

wiki_test.gno

10.64 Kb · 347 lines
  1package wiki
  2
  3import (
  4	"testing"
  5	"time"
  6
  7	"gno.land/p/nt/testutils/v0"
  8	"gno.land/p/nt/uassert/v0"
  9)
 10
 11var (
 12	alice = testutils.TestAddress("alice")
 13	bob   = testutils.TestAddress("bob")
 14	mod   = testutils.TestAddress("mod")
 15)
 16
 17// at returns a deterministic timestamp and height for step n, so tests never
 18// depend on wall-clock time or on where a previous test left the block height.
 19func at(n int) (time.Time, int64) {
 20	return time.Unix(int64(1757000000+n*60), 0).UTC(), int64(100 + n)
 21}
 22
 23func edit(t *testing.T, w *Wiki, n int, author address, title, body, summary string) *Revision {
 24	t.Helper()
 25	now, h := at(n)
 26	rev, err := w.Edit(author, now, h, title, body, summary, false)
 27	uassert.NoError(t, err, title)
 28	return rev
 29}
 30
 31func TestEditCreatesAndUpdates(t *testing.T) {
 32	w := New(DefaultRetention, DefaultMaxBody)
 33
 34	r1 := edit(t, w, 1, alice, "gno_land", "The first draft.\n", "create")
 35	uassert.Equal(t, uint64(1), r1.ID)
 36	uassert.Equal(t, uint64(0), r1.Prev)
 37	uassert.Equal(t, string(KindCreate), string(r1.Kind))
 38
 39	r2 := edit(t, w, 2, bob, "Gno land", "The second draft.\n", "expand")
 40	uassert.Equal(t, uint64(1), r2.Prev)
 41	uassert.Equal(t, string(KindEdit), string(r2.Kind))
 42
 43	p, err := w.Page("Gno land")
 44	uassert.NoError(t, err)
 45	uassert.Equal(t, 2, p.NumRevisions())
 46	body, kept := p.Body()
 47	uassert.True(t, kept)
 48	uassert.Equal(t, "The second draft.\n", body)
 49
 50	s := w.Stats()
 51	uassert.Equal(t, 1, s.Pages, "the two edits are one page")
 52	uassert.Equal(t, 2, s.Revisions)
 53}
 54
 55func TestEditRejectsNoOpAndOversize(t *testing.T) {
 56	w := New(DefaultRetention, 32)
 57	now, h := at(1)
 58
 59	_, err := w.Edit(alice, now, h, "A", "", "empty", false)
 60	uassert.ErrorIs(t, err, ErrEmptyBody)
 61
 62	_, err = w.Edit(alice, now, h, "A", "0123456789012345678901234567890123", "too big", false)
 63	uassert.ErrorIs(t, err, ErrBodyTooLarge)
 64
 65	edit(t, w, 1, alice, "A", "same\n", "create")
 66	_, err = w.Edit(alice, now, h, "A", "same\n", "again", false)
 67	uassert.ErrorIs(t, err, ErrNoChange, "an identical body must not create a revision")
 68
 69	_, err = w.Edit(alice, now, h, "Special:Anything", "x\n", "", false)
 70	uassert.ErrorIs(t, err, ErrSpecial)
 71}
 72
 73// TestRetentionEvictsOldBodies is the storage design in one test: the spine
 74// grows by a fixed amount per edit, the held bytes do not.
 75func TestRetentionEvictsOldBodies(t *testing.T) {
 76	w := New(2, DefaultMaxBody)
 77	bodies := []string{"aaaa\n", "bbbbbb\n", "cccccccc\n", "dddddddddd\n"}
 78	for i, b := range bodies {
 79		edit(t, w, i+1, alice, "A", b, "edit")
 80	}
 81
 82	p, err := w.Page("A")
 83	uassert.NoError(t, err)
 84	uassert.Equal(t, 4, p.NumRevisions(), "every revision stays in the history")
 85
 86	kept := 0
 87	for _, r := range p.History(0, 10) {
 88		if r.Kept() {
 89			kept++
 90		}
 91	}
 92	uassert.Equal(t, 2, kept, "only the retention window keeps bodies")
 93	uassert.Equal(t, len(bodies[2])+len(bodies[3]), w.Stats().BytesHeld)
 94
 95	// The spine of an evicted revision is intact, hash included.
 96	oldest := p.History(0, 10)[3]
 97	uassert.False(t, oldest.Kept())
 98	uassert.Equal(t, hashBody(bodies[0]), oldest.Hash)
 99	uassert.Equal(t, len(bodies[0]), oldest.Size)
100}
101
102func TestRevert(t *testing.T) {
103	w := New(3, DefaultMaxBody)
104	first := edit(t, w, 1, alice, "A", "good\n", "create")
105	edit(t, w, 2, bob, "A", "VANDALISM\n", "oops")
106
107	now, h := at(3)
108	rev, err := w.Revert(mod, now, h, "A", first.ID, "rv")
109	uassert.NoError(t, err)
110	uassert.Equal(t, string(KindRevert), string(rev.Kind))
111
112	p, _ := w.Page("A")
113	body, _ := p.Body()
114	uassert.Equal(t, "good\n", body)
115	uassert.Equal(t, 3, p.NumRevisions(), "the vandalism stays in the history")
116}
117
118func TestRevertRefusesEvictedAndUnknown(t *testing.T) {
119	w := New(1, DefaultMaxBody)
120	first := edit(t, w, 1, alice, "A", "one\n", "")
121	edit(t, w, 2, alice, "A", "two\n", "")
122	now, h := at(3)
123
124	_, err := w.Revert(alice, now, h, "A", first.ID, "")
125	uassert.ErrorIs(t, err, ErrBodyEvicted,
126		"reverting past the retention window must fail loudly, not silently")
127
128	_, err = w.Revert(alice, now, h, "A", 9999, "")
129	uassert.ErrorIs(t, err, ErrNoSuchRevision)
130}
131
132func TestBlankAndPurge(t *testing.T) {
133	w := New(5, DefaultMaxBody)
134	edit(t, w, 1, alice, "A", "content\n", "")
135	edit(t, w, 2, alice, "A", "more content\n", "")
136	uassert.Equal(t, 1, w.Stats().Pages)
137
138	now, h := at(3)
139	_, err := w.Blank(mod, now, h, "A", "policy")
140	uassert.NoError(t, err)
141	uassert.Equal(t, 0, w.Stats().Pages, "a blanked page stops counting as a page")
142
143	p, _ := w.Page("A")
144	uassert.True(t, p.Blanked)
145	uassert.Equal(t, 3, p.NumRevisions(), "blanking is a revision, not an erasure")
146
147	released, err := w.Purge("A")
148	uassert.NoError(t, err)
149	uassert.Equal(t, len("content\n")+len("more content\n"), released)
150	uassert.Equal(t, 0, w.Stats().BytesHeld, "purge releases every retained byte")
151	uassert.Equal(t, 3, p.NumRevisions(), "purge leaves the spine intact")
152}
153
154func TestBlankThenEditRestoresThePage(t *testing.T) {
155	w := New(5, DefaultMaxBody)
156	edit(t, w, 1, alice, "A", "content\n", "")
157	now, h := at(2)
158	w.Blank(mod, now, h, "A", "")
159	edit(t, w, 3, alice, "A", "back\n", "restore")
160	uassert.Equal(t, 1, w.Stats().Pages)
161	p, _ := w.Page("A")
162	uassert.False(t, p.Blanked)
163}
164
165func TestBacklinksAndRedlinks(t *testing.T) {
166	w := New(3, DefaultMaxBody)
167	edit(t, w, 1, alice, "A", "links to [[B]] and [[C]]\n", "")
168	edit(t, w, 2, alice, "D", "also links to [[B]]\n", "")
169
170	b := MustParseTitle("B")
171	in := w.Backlinks(b)
172	uassert.Equal(t, 2, len(in))
173	uassert.Equal(t, "A", in[0].String())
174	uassert.Equal(t, "D", in[1].String())
175
176	// C does not exist: a redlink is still indexed, so the page knows who is
177	// waiting for it the moment it is created.
178	uassert.False(t, w.Exists(MustParseTitle("C")))
179	uassert.Equal(t, 1, len(w.Backlinks(MustParseTitle("C"))))
180
181	// Editing A to drop the link must un-index it.
182	edit(t, w, 3, alice, "A", "links to nothing\n", "")
183	uassert.Equal(t, 1, len(w.Backlinks(b)))
184	uassert.Equal(t, 0, len(w.Backlinks(MustParseTitle("C"))))
185}
186
187func TestCategories(t *testing.T) {
188	w := New(3, DefaultMaxBody)
189	edit(t, w, 1, alice, "A", "text\n[[Category:Chains]]\n", "")
190	edit(t, w, 2, alice, "B", "text\n[[Category:Chains]]\n[[Category:Tools]]\n", "")
191
192	chains := MustParseTitle("Category:Chains")
193	uassert.Equal(t, 2, len(w.CategoryMembers(chains)))
194	uassert.Equal(t, 2, len(w.Categories()))
195
196	// An explicit [[:Category:X]] links to the category instead of joining it.
197	edit(t, w, 3, alice, "C", "see [[:Category:Chains]]\n", "")
198	uassert.Equal(t, 2, len(w.CategoryMembers(chains)))
199	uassert.Equal(t, 1, len(w.Backlinks(chains)))
200}
201
202func TestRedirect(t *testing.T) {
203	w := New(3, DefaultMaxBody)
204	edit(t, w, 1, alice, "Gno", "#REDIRECT [[Gno land]]\n", "")
205	edit(t, w, 2, alice, "Gno land", "The article.\n", "")
206
207	dest, asked, err := w.Resolve("Gno")
208	uassert.NoError(t, err)
209	uassert.Equal(t, "Gno land", dest.Title.String())
210	uassert.Equal(t, "Gno", asked.Title.String())
211
212	// A dangling redirect renders itself rather than 404ing.
213	edit(t, w, 3, alice, "Nowhere", "#REDIRECT [[Absent]]\n", "")
214	dest, _, err = w.Resolve("Nowhere")
215	uassert.NoError(t, err)
216	uassert.Equal(t, "Nowhere", dest.Title.String())
217}
218
219func TestMoveKeepsHistoryAndLeavesARedirect(t *testing.T) {
220	w := New(3, DefaultMaxBody)
221	edit(t, w, 1, alice, "Old name", "body linking to [[B]]\n", "")
222	edit(t, w, 2, alice, "Old name", "body linking to [[B]], revised\n", "")
223
224	now, h := at(3)
225	uassert.NoError(t, w.Move(mod, now, h, "Old name", "New name", "rename"))
226
227	moved, err := w.Page("New name")
228	uassert.NoError(t, err)
229	uassert.Equal(t, 3, moved.NumRevisions(), "history follows the page")
230	body, kept := moved.Body()
231	uassert.True(t, kept)
232	uassert.Equal(t, "body linking to [[B]], revised\n", body, "a move does not touch the text")
233
234	stub, err := w.Page("Old name")
235	uassert.NoError(t, err)
236	uassert.Equal(t, "New name", stub.Redirect())
237
238	// The backlink index must follow the rename, not point at the old key.
239	in := w.Backlinks(MustParseTitle("B"))
240	uassert.Equal(t, 1, len(in))
241	uassert.Equal(t, "New name", in[0].String())
242}
243
244func TestMoveRefusesAnExistingTarget(t *testing.T) {
245	w := New(3, DefaultMaxBody)
246	edit(t, w, 1, alice, "A", "a\n", "")
247	edit(t, w, 2, alice, "B", "b\n", "")
248	now, h := at(3)
249	uassert.ErrorIs(t, w.Move(mod, now, h, "A", "B", ""), ErrPageExists)
250}
251
252func TestProtectionIsRecordedInTheHistory(t *testing.T) {
253	w := New(3, DefaultMaxBody)
254	edit(t, w, 1, alice, "A", "a\n", "")
255	now, h := at(2)
256	uassert.NoError(t, w.SetProtection(mod, now, h, "A", "locked"))
257
258	p, _ := w.Page("A")
259	uassert.Equal(t, "locked", p.Protection.String())
260	uassert.Equal(t, 2, p.NumRevisions())
261
262	// The protection entry must not become the current text.
263	body, kept := p.Body()
264	uassert.True(t, kept)
265	uassert.Equal(t, "a\n", body)
266
267	// It carries the current hash, so the history does not read as a blanking.
268	last := p.History(0, 1)[0]
269	uassert.Equal(t, string(KindProtect), string(last.Kind))
270	uassert.Equal(t, hashBody("a\n"), last.Hash)
271}
272
273func TestMetaRevisionsDoNotEvictRealBodies(t *testing.T) {
274	w := New(2, DefaultMaxBody)
275	edit(t, w, 1, alice, "A", "one\n", "")
276	edit(t, w, 2, alice, "A", "two\n", "")
277	now, h := at(3)
278	w.SetProtection(mod, now, h, "A", "locked")
279	w.SetProtection(mod, now, h, "A", "open")
280
281	p, _ := w.Page("A")
282	kept := 0
283	for _, r := range p.History(0, 10) {
284		if r.Kept() {
285			kept++
286		}
287	}
288	uassert.Equal(t, 2, kept, "two bodyless entries must not push both bodies out of a window of 2")
289}
290
291func TestRecentChangesIsNewestFirst(t *testing.T) {
292	w := New(3, DefaultMaxBody)
293	edit(t, w, 1, alice, "A", "a\n", "first")
294	edit(t, w, 2, bob, "B", "b\n", "second")
295
296	recent := w.Recent(10)
297	uassert.Equal(t, 2, len(recent))
298	uassert.Equal(t, "B", recent[0].Title.String())
299	uassert.Equal(t, "A", recent[1].Title.String())
300}
301
302func TestTitlesWalkOneNamespace(t *testing.T) {
303	w := New(3, DefaultMaxBody)
304	edit(t, w, 1, alice, "Beta", "b\n", "")
305	edit(t, w, 2, alice, "Alpha", "a\n", "")
306	edit(t, w, 3, alice, "Help:Alpha", "t\n", "")
307
308	main := w.Titles(NSMain.Prefix(), 0, 10)
309	uassert.Equal(t, 2, len(main))
310	uassert.Equal(t, "Alpha", main[0].String())
311	uassert.Equal(t, "Beta", main[1].String())
312
313	help := w.Titles(NSHelp.Prefix(), 0, 10)
314	uassert.Equal(t, 1, len(help))
315	uassert.Equal(t, "Help:Alpha", help[0].String())
316
317	uassert.Equal(t, 1, len(w.Titles(NSMain.Prefix(), 1, 10)), "offset skips")
318}
319
320func TestContributors(t *testing.T) {
321	w := New(3, DefaultMaxBody)
322	edit(t, w, 1, alice, "A", "1\n", "")
323	edit(t, w, 2, bob, "A", "2\n", "")
324	edit(t, w, 3, alice, "A", "3\n", "")
325
326	p, _ := w.Page("A")
327	c := p.Contributors()
328	uassert.Equal(t, 2, len(c))
329	uassert.Equal(t, alice.String(), c[0].String())
330	uassert.Equal(t, bob.String(), c[1].String())
331}
332
333func TestFormatGNOT(t *testing.T) {
334	cases := []struct {
335		in   int
336		want string
337	}{
338		{0, "0 GNOT"},
339		{1000000, "1 GNOT"},
340		{1500000, "1.5 GNOT"},
341		{100, "0.0001 GNOT"},
342		{123456, "0.123456 GNOT"},
343	}
344	for _, tc := range cases {
345		uassert.Equal(t, tc.want, formatGNOT(tc.in))
346	}
347}