PathrockNetwork Gno Explorer
HomeBlocksTransactionsTokensRealmsPackagesValidatorsAnalytics

PathrockNetwork Gno Explorer — an independent explorer for Gno.land Mainnet (gnoland-1), operated by PathrockNetwork. Not an official Gno.land service.

gnowebarchive RPC

gno.land/p/moul/x/wiki/v0

Package
Open in gnoweb ↗

Overview

Kind
Pure package
Name
v0
Namespace
moul / x / wiki
Files
18 (README)(gnomod.toml)
Exported functions
n/a — not supported for pure packages by the node (vm/qfuncs)
Module
gno.land/p/moul/x/wiki/v0
gno
0.9

Files (18)

  • README.mdmarkdown
  • gnomod.tomltoml
  • diff.gnogno
  • doc.gnogno
  • links.gnogno
  • page.gnogno
  • render.gnogno
  • revision.gnogno
  • talk.gnogno
  • title.gnogno
  • wiki.gnogno
  • diff_test.gnogno
  • limits_test.gnogno
  • links_test.gnogno
  • render_test.gnogno
  • talk_test.gnogno
  • title_test.gnogno
  • wiki_test.gnogno
  • wiki_test.gnogno
    1package wiki23import (4	"testing"5	"time"67	"gno.land/p/nt/testutils/v0"8

    Functions

    not supported for pure packages by the node (vm/qfuncs)

    Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.

    "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}
    348