Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test(boards2): unit tests for Board and Post types #3150

Merged
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
Loading