Skip to content

Commit

Permalink
test(boards2): unit tests for Board and Post types (#3150)
Browse files Browse the repository at this point in the history
Co-authored-by: x1unix <[email protected]>
  • Loading branch information
jeronimoalbi and x1unix authored Nov 20, 2024
1 parent dd21273 commit 1f6d61f
Show file tree
Hide file tree
Showing 4 changed files with 617 additions and 41 deletions.
4 changes: 4 additions & 0 deletions examples/gno.land/r/demo/boards2/board.gno
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ func (board *Board) IsPrivate() bool {
return board.id == 0
}

func (board *Board) GetID() BoardID {
return board.id
}

// GetURL returns the relative URL of the board.
func (board *Board) GetURL() string {
return strings.TrimPrefix(std.CurrentRealm().PkgPath(), "gno.land") + ":" + board.name
Expand Down
158 changes: 158 additions & 0 deletions examples/gno.land/r/demo/boards2/board_test.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
package boards

import (
"std"
"strings"
"testing"

"gno.land/p/demo/uassert"
"gno.land/p/moul/txlink"
)

func TestBoardID_String(t *testing.T) {
input := BoardID(32)

uassert.Equal(t, "32", input.String())
}

func TestBoardID_Key(t *testing.T) {
input := BoardID(128)
want := strings.Repeat("0", 7) + "128"
uassert.Equal(t, want, input.Key())
}

func TestBoard_IsPrivate(t *testing.T) {
b := new(Board)
b.id = 0
uassert.True(t, b.IsPrivate())

b.id = 128
uassert.False(t, b.IsPrivate())
}

func TestBoard_GetID(t *testing.T) {
want := int(92)
b := new(Board)
b.id = BoardID(want)
got := int(b.GetID())

uassert.Equal(t, got, want)
uassert.NotEqual(t, got, want*want)
}

func TestBoard_GetURL(t *testing.T) {
pkgPath := strings.TrimPrefix(std.CurrentRealm().PkgPath(), "gno.land")
name := "foobar_test_get_url123"
want := pkgPath + ":" + name

var addr std.Address

board := newBoard(1, name, addr)
got := board.GetURL()
uassert.Equal(t, want, got)
}

func TestBoard_GetThread(t *testing.T) {
var addr std.Address
b := newBoard(1, "test123", addr)

_, ok := b.GetThread(12345)
uassert.False(t, ok)

post := b.AddThread(addr, "foo", "bar")
_, ok = b.GetThread(post.GetPostID())
uassert.True(t, ok)
}

func TestBoard_DeleteThread(t *testing.T) {
var addr std.Address
b := newBoard(1, "test123", addr)

post := b.AddThread(addr, "foo", "bar")
id := post.GetPostID()

b.DeleteThread(id)

_, ok := b.GetThread(id)
uassert.False(t, ok)
}

func TestBoard_HasPermission(t *testing.T) {
var (
alice std.Address = "012345"
bob std.Address = "cafebabe"
)

cases := []struct {
label string
creator std.Address
actor std.Address
perm Permission
expect bool
}{
{
label: "creator should be able to edit board",
expect: true,
creator: alice,
actor: alice,
perm: PermissionEdit,
},
{
label: "creator should be able to delete board",
expect: true,
creator: alice,
actor: alice,
perm: PermissionDelete,
},
{
label: "guest shouldn't be able to edit boards",
expect: false,
creator: alice,
actor: bob,
perm: PermissionEdit,
},
{
label: "guest shouldn't be able to delete boards",
expect: false,
creator: alice,
actor: bob,
perm: PermissionDelete,
},
}

for i, c := range cases {
t.Run(c.label, func(t *testing.T) {
b := newBoard(BoardID(i), "test12345", c.creator)
got := b.HasPermission(c.actor, c.perm)
uassert.Equal(t, c.expect, got)
})
}
}

var boardUrlPrefix = strings.TrimPrefix(std.CurrentRealm().PkgPath(), "gno.land")

func TestBoard_GetURLFromThreadID(t *testing.T) {
boardName := "test12345"
b := newBoard(BoardID(11), boardName, "")
want := boardUrlPrefix + ":" + boardName + "/10"

got := b.GetURLFromThreadID(10)
uassert.Equal(t, want, got)
}

func TestBoard_GetURLFromReplyID(t *testing.T) {
boardName := "test12345"
b := newBoard(BoardID(11), boardName, "")
want := boardUrlPrefix + ":" + boardName + "/10/20"

got := b.GetURLFromReplyID(10, 20)
uassert.Equal(t, want, got)
}

func TestBoard_GetPostFormURL(t *testing.T) {
bid := BoardID(386)
b := newBoard(bid, "foo1234", "")
expect := txlink.URL("CreateThread", "bid", bid.String())
got := b.GetPostFormURL()
uassert.Equal(t, expect, got)
}
121 changes: 80 additions & 41 deletions examples/gno.land/r/demo/boards2/post.gno
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package boards

import (
"errors"
"std"
"strconv"
"time"
Expand All @@ -24,46 +25,78 @@ func (id PostID) Key() string {
// A Post is a "thread" or a "reply" depending on context.
// A thread is a Post of a Board that holds other replies.
type Post struct {
board *Board
id PostID
creator std.Address
title string // optional
body string
replies avl.Tree // Post.id -> *Post
repliesAll avl.Tree // Post.id -> *Post (all replies, for top-level posts)
reposts avl.Tree // Board.id -> Post.id
threadID PostID // original Post.id
parentID PostID // parent Post.id (if reply or repost)
repostBoard BoardID // original Board.id (if repost)
createdAt time.Time
updatedAt time.Time
board *Board
id PostID
creator std.Address
title string // optional
body string
replies avl.Tree // Post.id -> *Post
repliesAll avl.Tree // Post.id -> *Post (all replies, for top-level posts)
reposts avl.Tree // Board.id -> Post.id
threadID PostID // original Post.id
parentID PostID // parent Post.id (if reply or repost)
repostBoardID BoardID // original Board.id (if repost)
createdAt time.Time
updatedAt time.Time
}

func newPost(board *Board, id PostID, creator std.Address, title, body string, threadID, parentID PostID, repostBoard BoardID) *Post {
func newPost(board *Board, id PostID, creator std.Address, title, body string, threadID, parentID PostID, repostBoardID BoardID) *Post {
return &Post{
board: board,
id: id,
creator: creator,
title: title,
body: body,
replies: avl.Tree{},
repliesAll: avl.Tree{},
reposts: avl.Tree{},
threadID: threadID,
parentID: parentID,
repostBoard: repostBoard,
createdAt: time.Now(),
board: board,
id: id,
creator: creator,
title: title,
body: body,
replies: avl.Tree{},
repliesAll: avl.Tree{},
reposts: avl.Tree{},
threadID: threadID,
parentID: parentID,
repostBoardID: repostBoardID,
createdAt: time.Now(),
}
}

func (post *Post) IsThread() bool {
return post.parentID == 0
}

func (post *Post) GetBoard() *Board {
return post.board
}

func (post *Post) GetPostID() PostID {
return post.id
}

func (post *Post) GetParentID() PostID {
return post.parentID
}

func (post *Post) GetRepostBoardID() BoardID {
return post.repostBoardID
}

func (post *Post) GetCreator() std.Address {
return post.creator
}

func (post *Post) GetTitle() string {
return post.title
}

func (post *Post) GetBody() string {
return post.body
}

func (post *Post) GetCreatedAt() time.Time {
return post.createdAt
}

func (post *Post) GetUpdatedAt() time.Time {
return post.updatedAt
}

func (post *Post) AddReply(creator std.Address, body string) *Post {
board := post.board
pid := board.incGetPostID()
Expand Down Expand Up @@ -108,24 +141,30 @@ func (post *Post) AddRepostTo(creator std.Address, title, body string, dst *Boar
return repost
}

func (thread *Post) DeleteReply(replyID PostID) {
if thread.id == replyID {
panic("should not happen")
func (post *Post) DeleteReply(replyID PostID) error {
if !post.IsThread() {
// TODO: Allow removing replies from parent replies too
panic("cannot delete reply from a non-thread post")
}

if post.id == replyID {
return errors.New("expected an ID of an inner reply")
}

key := replyID.Key()
v, removed := thread.repliesAll.Remove(key)
v, removed := post.repliesAll.Remove(key)
if !removed {
panic("reply not found in thread")
return errors.New("reply not found in thread")
}

post := v.(*Post)
if post.parentID != thread.id {
parent, _ := thread.GetReply(post.parentID)
reply := v.(*Post)
if reply.parentID != post.id {
parent, _ := post.GetReply(reply.parentID)
parent.replies.Remove(key)
} else {
thread.replies.Remove(key)
post.replies.Remove(key)
}
return nil
}

// TODO: Change HasPermission to use a new authorization interface's `CanDo()`
Expand Down Expand Up @@ -158,15 +197,15 @@ func (post *Post) GetURL() string {
func (post *Post) GetReplyFormURL() string {
return txlink.URL("CreateReply",
"bid", post.board.id.String(),
"threadid", post.threadID.String(),
"postid", post.id.String(),
"threadID", post.threadID.String(),
"postID", post.id.String(),
)
}

func (post *Post) GetRepostFormURL() string {
return txlink.URL("CreateRepost",
"bid", post.board.id.String(),
"postid", post.id.String(),
"postID", post.id.String(),
)
}

Expand All @@ -185,10 +224,10 @@ func (post *Post) GetDeleteFormURL() string {
}

func (post *Post) RenderSummary() string {
if post.repostBoard != 0 {
dstBoard, found := getBoard(post.repostBoard)
if post.repostBoardID != 0 {
dstBoard, found := getBoard(post.repostBoardID)
if !found {
panic("repostBoard does not exist")
panic("repost board does not exist")
}

thread, found := dstBoard.GetThread(PostID(post.parentID))
Expand Down
Loading

0 comments on commit 1f6d61f

Please sign in to comment.