github.com/Azareal/Gosora@v0.0.0-20210729070923-553e66b59003/misc_test.go (about)

     1  package main
     2  
     3  import (
     4  	"bytes"
     5  	"database/sql"
     6  	"fmt"
     7  	"io/ioutil"
     8  	"net/http"
     9  	"net/http/httptest"
    10  	"os"
    11  	"runtime/debug"
    12  	"strconv"
    13  	"testing"
    14  	"time"
    15  
    16  	c "github.com/Azareal/Gosora/common"
    17  	"github.com/Azareal/Gosora/common/gauth"
    18  	"github.com/Azareal/Gosora/common/phrases"
    19  	"github.com/Azareal/Gosora/routes"
    20  	"github.com/pkg/errors"
    21  )
    22  
    23  func miscinit(t *testing.T) {
    24  	if err := gloinit(); err != nil {
    25  		t.Fatal(err)
    26  	}
    27  }
    28  
    29  func recordMustExist(t *testing.T, err error, errmsg string, args ...interface{}) {
    30  	if err == ErrNoRows {
    31  		debug.PrintStack()
    32  		t.Errorf(errmsg, args...)
    33  	} else if err != nil {
    34  		debug.PrintStack()
    35  		t.Fatal(err)
    36  	}
    37  }
    38  
    39  func recordMustNotExist(t *testing.T, err error, errmsg string, args ...interface{}) {
    40  	if err == nil {
    41  		debug.PrintStack()
    42  		t.Errorf(errmsg, args...)
    43  	} else if err != ErrNoRows {
    44  		debug.PrintStack()
    45  		t.Fatal(err)
    46  	}
    47  }
    48  
    49  func TestUserStore(t *testing.T) {
    50  	miscinit(t)
    51  	if !c.PluginsInited {
    52  		c.InitPlugins()
    53  	}
    54  
    55  	var err error
    56  	uc := c.NewMemoryUserCache(c.Config.UserCacheCapacity)
    57  	c.Users, err = c.NewDefaultUserStore(uc)
    58  	expectNilErr(t, err)
    59  	uc.Flush()
    60  	userStoreTest(t, 2)
    61  	c.Users, err = c.NewDefaultUserStore(nil)
    62  	expectNilErr(t, err)
    63  	userStoreTest(t, 5)
    64  }
    65  func userStoreTest(t *testing.T, newUserID int) {
    66  	uc := c.Users.GetCache()
    67  	// Go doesn't have short-circuiting, so this'll allow us to do one liner tests
    68  	cacheLength := func(uc c.UserCache) int {
    69  		if uc == nil {
    70  			return 0
    71  		}
    72  		return uc.Length()
    73  	}
    74  	isCacheLengthZero := func(uc c.UserCache) bool {
    75  		return cacheLength(uc) == 0
    76  	}
    77  	ex, exf := exp(t), expf(t)
    78  	exf(isCacheLengthZero(uc), "The initial ucache length should be zero, not %d", cacheLength(uc))
    79  
    80  	_, err := c.Users.Get(-1)
    81  	recordMustNotExist(t, err, "UID #-1 shouldn't exist")
    82  	exf(isCacheLengthZero(uc), "We found %d items in the user cache and it's supposed to be empty", cacheLength(uc))
    83  	_, err = c.Users.Get(0)
    84  	recordMustNotExist(t, err, "UID #0 shouldn't exist")
    85  	exf(isCacheLengthZero(uc), "We found %d items in the user cache and it's supposed to be empty", cacheLength(uc))
    86  
    87  	user, err := c.Users.Get(1)
    88  	recordMustExist(t, err, "Couldn't find UID #1")
    89  
    90  	expectW := func(cond, expec bool, prefix, suffix string) {
    91  		midfix := "should not be"
    92  		if expec {
    93  			midfix = "should be"
    94  		}
    95  		ex(cond, prefix+" "+midfix+" "+suffix)
    96  	}
    97  
    98  	// TODO: Add email checks too? Do them separately?
    99  	expectUser := func(u *c.User, uid int, name string, group int, super, admin, mod, banned bool) {
   100  		exf(u.ID == uid, "u.ID should be %d. Got '%d' instead.", uid, u.ID)
   101  		exf(u.Name == name, "u.Name should be '%s', not '%s'", name, u.Name)
   102  		expectW(u.Group == group, true, u.Name, "in group"+strconv.Itoa(group))
   103  		expectW(u.IsSuperAdmin == super, super, u.Name, "a super admin")
   104  		expectW(u.IsAdmin == admin, admin, u.Name, "an admin")
   105  		expectW(u.IsSuperMod == mod, mod, u.Name, "a super mod")
   106  		expectW(u.IsMod == mod, mod, u.Name, "a mod")
   107  		expectW(u.IsBanned == banned, banned, u.Name, "banned")
   108  	}
   109  	expectUser(user, 1, "Admin", 1, true, true, true, false)
   110  
   111  	user, err = c.Users.GetByName("Admin")
   112  	recordMustExist(t, err, "Couldn't find user 'Admin'")
   113  	expectUser(user, 1, "Admin", 1, true, true, true, false)
   114  	us, err := c.Users.BulkGetByName([]string{"Admin"})
   115  	recordMustExist(t, err, "Couldn't find user 'Admin'")
   116  	exf(len(us) == 1, "len(us) should be 1, not %d", len(us))
   117  	expectUser(us[0], 1, "Admin", 1, true, true, true, false)
   118  
   119  	_, err = c.Users.Get(newUserID)
   120  	recordMustNotExist(t, err, fmt.Sprintf("UID #%d shouldn't exist", newUserID))
   121  
   122  	// TODO: GetByName tests for newUserID
   123  
   124  	if uc != nil {
   125  		expectIntToBeX(t, uc.Length(), 1, "User cache length should be 1, not %d")
   126  		_, err = uc.Get(-1)
   127  		recordMustNotExist(t, err, "UID #-1 shouldn't exist, even in the cache")
   128  		_, err = uc.Get(0)
   129  		recordMustNotExist(t, err, "UID #0 shouldn't exist, even in the cache")
   130  		user, err = uc.Get(1)
   131  		recordMustExist(t, err, "Couldn't find UID #1 in the cache")
   132  
   133  		exf(user.ID == 1, "user.ID does not match the requested UID. Got '%d' instead.", user.ID)
   134  		exf(user.Name == "Admin", "user.Name should be 'Admin', not '%s'", user.Name)
   135  
   136  		_, err = uc.Get(newUserID)
   137  		recordMustNotExist(t, err, "UID #%d shouldn't exist, even in the cache", newUserID)
   138  		uc.Flush()
   139  		expectIntToBeX(t, uc.Length(), 0, "User cache length should be 0, not %d")
   140  	}
   141  
   142  	// TODO: Lock onto the specific error type. Is this even possible without sacrificing the detailed information in the error message?
   143  	bulkGetMapEmpty := func(id int) {
   144  		userList, _ := c.Users.BulkGetMap([]int{id})
   145  		exf(len(userList) == 0, "The userList length should be 0, not %d", len(userList))
   146  		exf(isCacheLengthZero(uc), "User cache length should be 0, not %d", cacheLength(uc))
   147  	}
   148  	bulkGetMapEmpty(-1)
   149  	bulkGetMapEmpty(0)
   150  
   151  	userList, _ := c.Users.BulkGetMap([]int{1})
   152  	exf(len(userList) == 1, "Returned map should have one result (UID #1), not %d", len(userList))
   153  	user, ok := userList[1]
   154  	if !ok {
   155  		t.Error("We couldn't find UID #1 in the returned map")
   156  		t.Error("userList", userList)
   157  		return
   158  	}
   159  	exf(user.ID == 1, "user.ID does not match the requested UID. Got '%d' instead.", user.ID)
   160  
   161  	if uc != nil {
   162  		expectIntToBeX(t, uc.Length(), 1, "User cache length should be 1, not %d")
   163  		user, err = uc.Get(1)
   164  		recordMustExist(t, err, "Couldn't find UID #1 in the cache")
   165  
   166  		exf(user.ID == 1, "user.ID does not match the requested UID. Got '%d' instead.", user.ID)
   167  		uc.Flush()
   168  	}
   169  
   170  	ex(!c.Users.Exists(-1), "UID #-1 shouldn't exist")
   171  	ex(!c.Users.Exists(0), "UID #0 shouldn't exist")
   172  	ex(c.Users.Exists(1), "UID #1 should exist")
   173  	exf(!c.Users.Exists(newUserID), "UID #%d shouldn't exist", newUserID)
   174  
   175  	exf(isCacheLengthZero(uc), "User cache length should be 0, not %d", cacheLength(uc))
   176  	expectIntToBeX(t, c.Users.Count(), 1, "The number of users should be 1, not %d")
   177  	searchUser := func(name, email string, gid, count int) {
   178  		f := func(name, email string, gid, count int, m string) {
   179  			expectIntToBeX(t, c.Users.CountSearch(name, email, gid), count, "The number of users for "+m+", not %d")
   180  		}
   181  		f(name, email, 0, count, fmt.Sprintf("name '%s' and email '%s' should be %d", name, email, count))
   182  		f(name, "", 0, count, fmt.Sprintf("name '%s' should be %d", name, count))
   183  		f("", email, 0, count, fmt.Sprintf("email '%s' should be %d", email, count))
   184  
   185  		f2 := func(name, email string, gid, offset int, m string, args ...interface{}) {
   186  			ulist, err := c.Users.SearchOffset(name, email, gid, offset, 15)
   187  			expectNilErr(t, err)
   188  			expectIntToBeX(t, len(ulist), count, "The number of users for "+fmt.Sprintf(m, args...)+", not %d")
   189  		}
   190  		f2(name, email, 0, 0, "name '%s' and email '%s' should be %d", name, email, count)
   191  		f2(name, "", 0, 0, "name '%s' should be %d", name, count)
   192  		f2("", email, 0, 0, "email '%s' should be %d", email, count)
   193  
   194  		count = 0
   195  		f2(name, email, 0, 10, "name '%s' and email '%s' should be %d", name, email, count)
   196  		f2(name, "", 0, 10, "name '%s' should be %d", name, count)
   197  		f2("", email, 0, 10, "email '%s' should be %d", email, count)
   198  
   199  		f2(name, email, 999, 0, "name '%s' and email '%s' should be %d", name, email, 0)
   200  		f2(name, "", 999, 0, "name '%s' should be %d", name, 0)
   201  		f2("", email, 999, 0, "email '%s' should be %d", email, 0)
   202  
   203  		f2(name, email, 999, 10, "name '%s' and email '%s' should be %d", name, email, 0)
   204  		f2(name, "", 999, 10, "name '%s' should be %d", name, 0)
   205  		f2("", email, 999, 10, "email '%s' should be %d", email, 0)
   206  	}
   207  	searchUser("Sam", "sam@localhost.loc", 0, 0)
   208  	// TODO: CountSearch gid test
   209  
   210  	awaitingActivation := 5
   211  	// TODO: Write tests for the registration validators
   212  	uid, err := c.Users.Create("Sam", "ReallyBadPassword", "sam@localhost.loc", awaitingActivation, false)
   213  	expectNilErr(t, err)
   214  	exf(uid == newUserID, "The UID of the new user should be %d not %d", newUserID, uid)
   215  	exf(c.Users.Exists(newUserID), "UID #%d should exist", newUserID)
   216  	expectIntToBeX(t, c.Users.Count(), 2, "The number of users should be 2, not %d")
   217  	searchUser("Sam", "sam@localhost.loc", 0, 1)
   218  	// TODO: CountSearch gid test
   219  
   220  	user, err = c.Users.Get(newUserID)
   221  	recordMustExist(t, err, "Couldn't find UID #%d", newUserID)
   222  	expectUser(user, newUserID, "Sam", 5, false, false, false, false)
   223  
   224  	if uc != nil {
   225  		expectIntToBeX(t, uc.Length(), 1, "User cache length should be 1, not %d")
   226  		user, err = uc.Get(newUserID)
   227  		recordMustExist(t, err, "Couldn't find UID #%d in the cache", newUserID)
   228  		exf(user.ID == newUserID, "user.ID does not match the requested UID. Got '%d' instead.", user.ID)
   229  	}
   230  
   231  	userList, _ = c.Users.BulkGetMap([]int{1, uid})
   232  	exf(len(userList) == 2, "Returned map should have 2 results, not %d", len(userList))
   233  	// TODO: More tests on userList
   234  
   235  	{
   236  		userList, _ := c.Users.BulkGetByName([]string{"Admin", "Sam"})
   237  		exf(len(userList) == 2, "Returned list should have 2 results, not %d", len(userList))
   238  	}
   239  
   240  	if uc != nil {
   241  		expectIntToBeX(t, uc.Length(), 2, "User cache length should be 2, not %d")
   242  		user, err = uc.Get(1)
   243  		recordMustExist(t, err, "Couldn't find UID #%d in the cache", 1)
   244  		exf(user.ID == 1, "user.ID does not match the requested UID. Got '%d' instead.", user.ID)
   245  		user, err = uc.Get(newUserID)
   246  		recordMustExist(t, err, "Couldn't find UID #%d in the cache", newUserID)
   247  		exf(user.ID == newUserID, "user.ID does not match the requested UID. Got '%d' instead.", user.ID)
   248  		uc.Flush()
   249  	}
   250  
   251  	user, err = c.Users.Get(newUserID)
   252  	recordMustExist(t, err, "Couldn't find UID #%d", newUserID)
   253  	expectUser(user, newUserID, "Sam", 5, false, false, false, false)
   254  
   255  	if uc != nil {
   256  		expectIntToBeX(t, uc.Length(), 1, "User cache length should be 1, not %d")
   257  		user, err = uc.Get(newUserID)
   258  		recordMustExist(t, err, "Couldn't find UID #%d in the cache", newUserID)
   259  		exf(user.ID == newUserID, "user.ID does not match the requested UID. Got '%d' instead.", user.ID)
   260  	}
   261  
   262  	expectNilErr(t, user.Activate())
   263  	expectIntToBeX(t, user.Group, 5, "Sam should still be in group 5 in this copy")
   264  
   265  	// ? - What if we change the caching mechanism so it isn't hard purged and reloaded? We'll deal with that when we come to it, but for now, this is a sign of a cache bug
   266  	afterUserFlush := func(uid int) {
   267  		if uc != nil {
   268  			expectIntToBeX(t, uc.Length(), 0, "User cache length should be 0, not %d")
   269  			_, err = uc.Get(uid)
   270  			recordMustNotExist(t, err, "UID #%d shouldn't be in the cache", uid)
   271  		}
   272  	}
   273  	afterUserFlush(newUserID)
   274  
   275  	user, err = c.Users.Get(newUserID)
   276  	recordMustExist(t, err, "Couldn't find UID #%d", newUserID)
   277  	expectUser(user, newUserID, "Sam", c.Config.DefaultGroup, false, false, false, false)
   278  
   279  	// Permanent ban
   280  	duration, _ := time.ParseDuration("0")
   281  
   282  	// TODO: Attempt a double ban, double activation, and double unban
   283  	expectNilErr(t, user.Ban(duration, 1))
   284  	exf(user.Group == c.Config.DefaultGroup, "Sam should be in group %d, not %d", c.Config.DefaultGroup, user.Group)
   285  	afterUserFlush(newUserID)
   286  
   287  	user, err = c.Users.Get(newUserID)
   288  	recordMustExist(t, err, "Couldn't find UID #%d", newUserID)
   289  	expectUser(user, newUserID, "Sam", c.BanGroup, false, false, false, true)
   290  
   291  	// TODO: Do tests against the scheduled updates table and the task system to make sure the ban exists there and gets revoked when it should
   292  
   293  	expectNilErr(t, user.Unban())
   294  	expectIntToBeX(t, user.Group, c.BanGroup, "Sam should still be in the ban group in this copy")
   295  	afterUserFlush(newUserID)
   296  
   297  	user, err = c.Users.Get(newUserID)
   298  	recordMustExist(t, err, "Couldn't find UID #%d", newUserID)
   299  	expectUser(user, newUserID, "Sam", c.Config.DefaultGroup, false, false, false, false)
   300  
   301  	reportsForumID := 1 // TODO: Use the constant in common?
   302  	generalForumID := 2
   303  	dummyResponseRecorder := httptest.NewRecorder()
   304  	bytesBuffer := bytes.NewBuffer([]byte(""))
   305  	dummyRequest1 := httptest.NewRequest("", "/forum/"+strconv.Itoa(reportsForumID), bytesBuffer)
   306  	dummyRequest2 := httptest.NewRequest("", "/forum/"+strconv.Itoa(generalForumID), bytesBuffer)
   307  	var user2 *c.User
   308  
   309  	changeGroupTest := func(oldGroup, newGroup int) {
   310  		expectNilErr(t, user.ChangeGroup(newGroup))
   311  		// ! I don't think ChangeGroup should be changing the value of user... Investigate this.
   312  		ex(oldGroup == user.Group, "Someone's mutated this pointer elsewhere")
   313  
   314  		user, err = c.Users.Get(newUserID)
   315  		recordMustExist(t, err, "Couldn't find UID #%d", newUserID)
   316  		user2 = c.BlankUser()
   317  		*user2 = *user
   318  	}
   319  
   320  	changeGroupTest2 := func(rank string, firstShouldBe, secondShouldBe bool) {
   321  		head, e := c.UserCheck(dummyResponseRecorder, dummyRequest1, user)
   322  		if e != nil {
   323  			t.Fatal(e)
   324  		}
   325  		head2, e := c.UserCheck(dummyResponseRecorder, dummyRequest2, user2)
   326  		if e != nil {
   327  			t.Fatal(e)
   328  		}
   329  		ferr := c.ForumUserCheck(head, dummyResponseRecorder, dummyRequest1, user, reportsForumID)
   330  		ex(ferr == nil, "There shouldn't be any errors in forumUserCheck")
   331  		ex(user.Perms.ViewTopic == firstShouldBe, rank+" should be able to access the reports forum")
   332  		ferr = c.ForumUserCheck(head2, dummyResponseRecorder, dummyRequest2, user2, generalForumID)
   333  		ex(ferr == nil, "There shouldn't be any errors in forumUserCheck")
   334  		ex(user2.Perms.ViewTopic == secondShouldBe, "Sam should be able to access the general forum")
   335  	}
   336  
   337  	changeGroupTest(c.Config.DefaultGroup, 1)
   338  	expectUser(user, newUserID, "Sam", 1, false, true, true, false)
   339  	changeGroupTest2("Admins", true, true)
   340  
   341  	changeGroupTest(1, 2)
   342  	expectUser(user, newUserID, "Sam", 2, false, false, true, false)
   343  	changeGroupTest2("Mods", true, true)
   344  
   345  	changeGroupTest(2, 3)
   346  	expectUser(user, newUserID, "Sam", 3, false, false, false, false)
   347  	changeGroupTest2("Members", false, true)
   348  	ex(user.Perms.ViewTopic != user2.Perms.ViewTopic, "user.Perms.ViewTopic and user2.Perms.ViewTopic should never match")
   349  
   350  	changeGroupTest(3, 4)
   351  	expectUser(user, newUserID, "Sam", 4, false, false, false, true)
   352  	changeGroupTest2("Members", false, true)
   353  
   354  	changeGroupTest(4, 5)
   355  	expectUser(user, newUserID, "Sam", 5, false, false, false, false)
   356  	changeGroupTest2("Members", false, true)
   357  
   358  	changeGroupTest(5, 6)
   359  	expectUser(user, newUserID, "Sam", 6, false, false, false, false)
   360  	changeGroupTest2("Members", false, true)
   361  
   362  	err = user.ChangeGroup(c.Config.DefaultGroup)
   363  	expectNilErr(t, err)
   364  	ex(user.Group == 6, "Someone's mutated this pointer elsewhere")
   365  
   366  	exf(user.LastIP == "", "user.LastIP should be blank not %s", user.LastIP)
   367  	expectNilErr(t, user.UpdateIP("::1"))
   368  	user, err = c.Users.Get(newUserID)
   369  	recordMustExist(t, err, "Couldn't find UID #%d", newUserID)
   370  	exf(user.LastIP == "::1", "user.LastIP should be %s not %s", "::1", user.LastIP)
   371  	expectNilErr(t, c.Users.ClearLastIPs())
   372  	expectNilErr(t, c.Users.Reload(newUserID))
   373  	user, err = c.Users.Get(newUserID)
   374  	recordMustExist(t, err, "Couldn't find UID #%d", newUserID)
   375  	exf(user.LastIP == "", "user.LastIP should be blank not %s", user.LastIP)
   376  
   377  	expectNilErr(t, user.Delete())
   378  	exf(!c.Users.Exists(newUserID), "UID #%d should no longer exist", newUserID)
   379  	afterUserFlush(newUserID)
   380  	expectIntToBeX(t, c.Users.Count(), 1, "The number of users should be 1, not %d")
   381  	searchUser("Sam", "sam@localhost.loc", 0, 0)
   382  	// TODO: CountSearch gid test
   383  
   384  	_, err = c.Users.Get(newUserID)
   385  	recordMustNotExist(t, err, "UID #%d shouldn't exist", newUserID)
   386  
   387  	// And a unicode test, even though I doubt it'll fail
   388  	uid, err = c.Users.Create("γ‚΅γƒ ", "πŸ˜€πŸ˜€πŸ˜€", "sam@localhost.loc", awaitingActivation, false)
   389  	expectNilErr(t, err)
   390  	exf(uid == newUserID+1, "The UID of the new user should be %d", newUserID+1)
   391  	exf(c.Users.Exists(newUserID+1), "UID #%d should exist", newUserID+1)
   392  
   393  	user, err = c.Users.Get(newUserID + 1)
   394  	recordMustExist(t, err, "Couldn't find UID #%d", newUserID+1)
   395  	expectUser(user, newUserID+1, "γ‚΅γƒ ", 5, false, false, false, false)
   396  
   397  	expectNilErr(t, user.Delete())
   398  	exf(!c.Users.Exists(newUserID+1), "UID #%d should no longer exist", newUserID+1)
   399  
   400  	// MySQL utf8mb4 username test
   401  	uid, err = c.Users.Create("πŸ˜€πŸ˜€πŸ˜€", "πŸ˜€πŸ˜€πŸ˜€", "sam@localhost.loc", awaitingActivation, false)
   402  	expectNilErr(t, err)
   403  	exf(uid == newUserID+2, "The UID of the new user should be %d", newUserID+2)
   404  	exf(c.Users.Exists(newUserID+2), "UID #%d should exist", newUserID+2)
   405  
   406  	user, err = c.Users.Get(newUserID + 2)
   407  	recordMustExist(t, err, "Couldn't find UID #%d", newUserID+1)
   408  	expectUser(user, newUserID+2, "πŸ˜€πŸ˜€πŸ˜€", 5, false, false, false, false)
   409  
   410  	expectNilErr(t, user.Delete())
   411  	exf(!c.Users.Exists(newUserID+2), "UID #%d should no longer exist", newUserID+2)
   412  
   413  	// TODO: Add unicode login tests somewhere? Probably with the rest of the auth tests
   414  	// TODO: Add tests for the Cache* methods
   415  }
   416  
   417  // TODO: Add an error message to this?
   418  func expectNilErr(t *testing.T, item error) {
   419  	if item != nil {
   420  		debug.PrintStack()
   421  		t.Fatal(item)
   422  	}
   423  }
   424  
   425  func expectIntToBeX(t *testing.T, item, expect int, errmsg string) {
   426  	if item != expect {
   427  		debug.PrintStack()
   428  		t.Fatalf(errmsg, item)
   429  	}
   430  }
   431  
   432  func expect(t *testing.T, item bool, errmsg string) {
   433  	if !item {
   434  		debug.PrintStack()
   435  		t.Fatal(errmsg)
   436  	}
   437  }
   438  
   439  func expectf(t *testing.T, item bool, errmsg string, args ...interface{}) {
   440  	if !item {
   441  		debug.PrintStack()
   442  		t.Fatalf(errmsg, args...)
   443  	}
   444  }
   445  
   446  func exp(t *testing.T) func(bool, string) {
   447  	return func(val bool, errmsg string) {
   448  		if !val {
   449  			debug.PrintStack()
   450  			t.Fatal(errmsg)
   451  		}
   452  	}
   453  }
   454  
   455  func expf(t *testing.T) func(bool, string, ...interface{}) {
   456  	return func(val bool, errmsg string, params ...interface{}) {
   457  		if !val {
   458  			debug.PrintStack()
   459  			t.Fatalf(errmsg, params...)
   460  		}
   461  	}
   462  }
   463  
   464  func TestPermsMiddleware(t *testing.T) {
   465  	miscinit(t)
   466  	if !c.PluginsInited {
   467  		c.InitPlugins()
   468  	}
   469  
   470  	dummyResponseRecorder := httptest.NewRecorder()
   471  	bytesBuffer := bytes.NewBuffer([]byte(""))
   472  	dummyRequest := httptest.NewRequest("", "/forum/1", bytesBuffer)
   473  	user := c.BlankUser()
   474  	ex := exp(t)
   475  
   476  	f := func(ff func(w http.ResponseWriter, r *http.Request, u *c.User) c.RouteError) bool {
   477  		ferr := ff(dummyResponseRecorder, dummyRequest, user)
   478  		return ferr == nil
   479  	}
   480  
   481  	ex(!f(c.SuperModOnly), "Blank users shouldn't be supermods")
   482  	user.IsSuperMod = false
   483  	ex(!f(c.SuperModOnly), "Non-supermods shouldn't be allowed through supermod gates")
   484  	user.IsSuperMod = true
   485  	ex(f(c.SuperModOnly), "Supermods should be allowed through supermod gates")
   486  
   487  	// TODO: Loop over the Control Panel routes and make sure only supermods can get in
   488  
   489  	user = c.BlankUser()
   490  
   491  	ex(!f(c.MemberOnly), "Blank users shouldn't be considered loggedin")
   492  	user.Loggedin = false
   493  	ex(!f(c.MemberOnly), "Guests shouldn't be able to access member areas")
   494  	user.Loggedin = true
   495  	ex(f(c.MemberOnly), "Logged in users should be able to access member areas")
   496  
   497  	// TODO: Loop over the /user/ routes and make sure only members can access the ones other than /user/username
   498  
   499  	user = c.BlankUser()
   500  
   501  	ex(!f(c.AdminOnly), "Blank users shouldn't be considered admins")
   502  	user.IsAdmin = false
   503  	ex(!f(c.AdminOnly), "Non-admins shouldn't be able to access admin areas")
   504  	user.IsAdmin = true
   505  	ex(f(c.AdminOnly), "Admins should be able to access admin areas")
   506  
   507  	user = c.BlankUser()
   508  
   509  	ex(!f(c.SuperAdminOnly), "Blank users shouldn't be considered super admins")
   510  	user.IsSuperAdmin = false
   511  	ex(!f(c.SuperAdminOnly), "Non-super admins shouldn't be allowed through the super admin gate")
   512  	user.IsSuperAdmin = true
   513  	ex(f(c.SuperAdminOnly), "Super admins should be allowed through super admin gates")
   514  
   515  	// TODO: Make sure only super admins can access the backups route
   516  
   517  	//dummyResponseRecorder = httptest.NewRecorder()
   518  	//bytesBuffer = bytes.NewBuffer([]byte(""))
   519  	//dummyRequest = httptest.NewRequest("", "/panel/backups/", bytesBuffer)
   520  }
   521  
   522  func TestTopicStore(t *testing.T) {
   523  	miscinit(t)
   524  	if !c.PluginsInited {
   525  		c.InitPlugins()
   526  	}
   527  
   528  	var err error
   529  	tcache := c.NewMemoryTopicCache(c.Config.TopicCacheCapacity)
   530  	c.Topics, err = c.NewDefaultTopicStore(tcache)
   531  	expectNilErr(t, err)
   532  	c.Config.DisablePostIP = false
   533  	topicStoreTest(t, 2, "::1")
   534  	c.Config.DisablePostIP = true
   535  	topicStoreTest(t, 3, "")
   536  
   537  	c.Topics, err = c.NewDefaultTopicStore(nil)
   538  	expectNilErr(t, err)
   539  	c.Config.DisablePostIP = false
   540  	topicStoreTest(t, 4, "::1")
   541  	c.Config.DisablePostIP = true
   542  	topicStoreTest(t, 5, "")
   543  }
   544  func topicStoreTest(t *testing.T, newID int, ip string) {
   545  	var topic *c.Topic
   546  	var err error
   547  	ex, exf := exp(t), expf(t)
   548  
   549  	_, err = c.Topics.Get(-1)
   550  	recordMustNotExist(t, err, "TID #-1 shouldn't exist")
   551  	_, err = c.Topics.Get(0)
   552  	recordMustNotExist(t, err, "TID #0 shouldn't exist")
   553  
   554  	topic, err = c.Topics.Get(1)
   555  	recordMustExist(t, err, "Couldn't find TID #1")
   556  	exf(topic.ID == 1, "topic.ID does not match the requested TID. Got '%d' instead.", topic.ID)
   557  
   558  	// TODO: Add BulkGetMap() to the TopicStore
   559  
   560  	ex(!c.Topics.Exists(-1), "TID #-1 shouldn't exist")
   561  	ex(!c.Topics.Exists(0), "TID #0 shouldn't exist")
   562  	ex(c.Topics.Exists(1), "TID #1 should exist")
   563  
   564  	count := c.Topics.Count()
   565  	exf(count == 1, "Global count for topics should be 1, not %d", count)
   566  
   567  	//Create(fid int, topicName string, content string, uid int, ip string) (tid int, err error)
   568  	tid, err := c.Topics.Create(2, "Test Topic", "Topic Content", 1, ip)
   569  	expectNilErr(t, err)
   570  	exf(tid == newID, "TID for the new topic should be %d, not %d", newID, tid)
   571  	exf(c.Topics.Exists(newID), "TID #%d should exist", newID)
   572  
   573  	count = c.Topics.Count()
   574  	exf(count == 2, "Global count for topics should be 2, not %d", count)
   575  
   576  	iFrag := func(cond bool) string {
   577  		if !cond {
   578  			return "n't"
   579  		}
   580  		return ""
   581  	}
   582  
   583  	testTopic := func(tid int, title, content string, createdBy int, ip string, parentID int, isClosed, sticky bool) {
   584  		topic, err = c.Topics.Get(tid)
   585  		recordMustExist(t, err, fmt.Sprintf("Couldn't find TID #%d", tid))
   586  		exf(topic.ID == tid, "topic.ID does not match the requested TID. Got '%d' instead.", topic.ID)
   587  		exf(topic.GetID() == tid, "topic.ID does not match the requested TID. Got '%d' instead.", topic.GetID())
   588  		exf(topic.Title == title, "The topic's name should be '%s', not %s", title, topic.Title)
   589  		exf(topic.Content == content, "The topic's body should be '%s', not %s", content, topic.Content)
   590  		exf(topic.CreatedBy == createdBy, "The topic's creator should be %d, not %d", createdBy, topic.CreatedBy)
   591  		exf(topic.IP == ip, "The topic's IP should be '%s', not %s", ip, topic.IP)
   592  		exf(topic.ParentID == parentID, "The topic's parent forum should be %d, not %d", parentID, topic.ParentID)
   593  		exf(topic.IsClosed == isClosed, "This topic should%s be locked", iFrag(topic.IsClosed))
   594  		exf(topic.Sticky == sticky, "This topic should%s be sticky", iFrag(topic.Sticky))
   595  		exf(topic.GetTable() == "topics", "The topic's table should be 'topics', not %s", topic.GetTable())
   596  	}
   597  
   598  	tc := c.Topics.GetCache()
   599  	shouldNotBeIn := func(tid int) {
   600  		if tc != nil {
   601  			_, err = tc.Get(tid)
   602  			recordMustNotExist(t, err, "Topic cache should be empty")
   603  		}
   604  	}
   605  	if tc != nil {
   606  		_, err = tc.Get(newID)
   607  		expectNilErr(t, err)
   608  	}
   609  
   610  	testTopic(newID, "Test Topic", "Topic Content", 1, ip, 2, false, false)
   611  
   612  	expectNilErr(t, topic.Lock())
   613  	shouldNotBeIn(newID)
   614  	testTopic(newID, "Test Topic", "Topic Content", 1, ip, 2, true, false)
   615  
   616  	expectNilErr(t, topic.Unlock())
   617  	shouldNotBeIn(newID)
   618  	testTopic(newID, "Test Topic", "Topic Content", 1, ip, 2, false, false)
   619  
   620  	expectNilErr(t, topic.Stick())
   621  	shouldNotBeIn(newID)
   622  	testTopic(newID, "Test Topic", "Topic Content", 1, ip, 2, false, true)
   623  
   624  	expectNilErr(t, topic.Unstick())
   625  	shouldNotBeIn(newID)
   626  	testTopic(newID, "Test Topic", "Topic Content", 1, ip, 2, false, false)
   627  
   628  	expectNilErr(t, topic.MoveTo(1))
   629  	shouldNotBeIn(newID)
   630  	testTopic(newID, "Test Topic", "Topic Content", 1, ip, 1, false, false)
   631  	// TODO: Add more tests for more *Topic methods
   632  
   633  	expectNilErr(t, c.Topics.ClearIPs())
   634  	expectNilErr(t, c.Topics.Reload(topic.ID))
   635  	testTopic(newID, "Test Topic", "Topic Content", 1, "", 1, false, false)
   636  	// TODO: Add more tests for more *Topic methods
   637  
   638  	expectNilErr(t, topic.Delete())
   639  	shouldNotBeIn(newID)
   640  
   641  	_, err = c.Topics.Get(newID)
   642  	recordMustNotExist(t, err, fmt.Sprintf("TID #%d shouldn't exist", newID))
   643  	exf(!c.Topics.Exists(newID), "TID #%d shouldn't exist", newID)
   644  
   645  	// TODO: Test topic creation and retrieving that created topic plus reload and inspecting the cache
   646  }
   647  
   648  func TestForumStore(t *testing.T) {
   649  	miscinit(t)
   650  	if !c.PluginsInited {
   651  		c.InitPlugins()
   652  	}
   653  	ex, exf := exp(t), expf(t)
   654  	// TODO: Test ForumStore.Reload
   655  
   656  	fcache, ok := c.Forums.(c.ForumCache)
   657  	ex(ok, "Unable to cast ForumStore to ForumCache")
   658  	ex(c.Forums.Count() == 2, "The forumstore global count should be 2")
   659  	ex(fcache.Length() == 2, "The forum cache length should be 2")
   660  
   661  	_, err := c.Forums.Get(-1)
   662  	recordMustNotExist(t, err, "FID #-1 shouldn't exist")
   663  	_, err = c.Forums.Get(0)
   664  	recordMustNotExist(t, err, "FID #0 shouldn't exist")
   665  
   666  	testForum := func(f *c.Forum, fid int, name string, active bool, desc string) {
   667  		exf(f.ID == fid, "forum.ID should be %d, not %d.", fid, f.ID)
   668  		// TODO: Check the preset and forum permissions
   669  		exf(f.Name == name, "forum.Name should be %s, not %s", name, f.Name)
   670  		str := ""
   671  		if !active {
   672  			str = "n't"
   673  		}
   674  		exf(f.Active == active, "The reports forum should%s be active", str)
   675  		exf(f.Desc == desc, "forum.Desc should be '%s' not '%s'", desc, f.Desc)
   676  	}
   677  
   678  	forum, err := c.Forums.Get(1)
   679  	recordMustExist(t, err, "Couldn't find FID #1")
   680  	expectDesc := "All the reports go here"
   681  	testForum(forum, 1, "Reports", false, expectDesc)
   682  	forum, err = c.Forums.BypassGet(1)
   683  	recordMustExist(t, err, "Couldn't find FID #1")
   684  
   685  	forum, err = c.Forums.Get(2)
   686  	recordMustExist(t, err, "Couldn't find FID #2")
   687  	forum, err = c.Forums.BypassGet(2)
   688  	recordMustExist(t, err, "Couldn't find FID #2")
   689  	expectDesc = "A place for general discussions which don't fit elsewhere"
   690  	testForum(forum, 2, "General", true, expectDesc)
   691  
   692  	// Forum reload test, kind of hacky but gets the job done
   693  	/*
   694  		CacheGet(id int) (*Forum, error)
   695  		CacheSet(forum *Forum) error
   696  	*/
   697  	ex(ok, "ForumCache should be available")
   698  	forum.Name = "nanana"
   699  	fcache.CacheSet(forum)
   700  	forum, err = c.Forums.Get(2)
   701  	recordMustExist(t, err, "Couldn't find FID #2")
   702  	exf(forum.Name == "nanana", "The faux name should be nanana not %s", forum.Name)
   703  	expectNilErr(t, c.Forums.Reload(2))
   704  	forum, err = c.Forums.Get(2)
   705  	recordMustExist(t, err, "Couldn't find FID #2")
   706  	exf(forum.Name == "General", "The proper name should be 2 not %s", forum.Name)
   707  
   708  	ex(!c.Forums.Exists(-1), "FID #-1 shouldn't exist")
   709  	ex(!c.Forums.Exists(0), "FID #0 shouldn't exist")
   710  	ex(c.Forums.Exists(1), "FID #1 should exist")
   711  	ex(c.Forums.Exists(2), "FID #2 should exist")
   712  	ex(!c.Forums.Exists(3), "FID #3 shouldn't exist")
   713  
   714  	_, err = c.Forums.Create("", "", true, "all")
   715  	ex(err != nil, "A forum shouldn't be successfully created, if it has a blank name")
   716  
   717  	fid, err := c.Forums.Create("Test Forum", "", true, "all")
   718  	expectNilErr(t, err)
   719  	ex(fid == 3, "The first forum we create should have an ID of 3")
   720  	ex(c.Forums.Exists(3), "FID #2 should exist")
   721  
   722  	ex(c.Forums.Count() == 3, "The forumstore global count should be 3")
   723  	ex(fcache.Length() == 3, "The forum cache length should be 3")
   724  
   725  	forum, err = c.Forums.Get(3)
   726  	recordMustExist(t, err, "Couldn't find FID #3")
   727  	forum, err = c.Forums.BypassGet(3)
   728  	recordMustExist(t, err, "Couldn't find FID #3")
   729  	testForum(forum, 3, "Test Forum", true, "")
   730  
   731  	// TODO: More forum creation tests
   732  
   733  	expectNilErr(t, c.Forums.Delete(3))
   734  	ex(forum.ID == 3, "forum pointer shenanigans")
   735  	ex(c.Forums.Count() == 2, "The forumstore global count should be 2")
   736  	ex(fcache.Length() == 2, "The forum cache length should be 2")
   737  	ex(!c.Forums.Exists(3), "FID #3 shouldn't exist after being deleted")
   738  	_, err = c.Forums.Get(3)
   739  	recordMustNotExist(t, err, "FID #3 shouldn't exist after being deleted")
   740  	_, err = c.Forums.BypassGet(3)
   741  	recordMustNotExist(t, err, "FID #3 shouldn't exist after being deleted")
   742  
   743  	ex(c.Forums.Delete(c.ReportForumID) != nil, "The reports forum shouldn't be deletable")
   744  	exf(c.Forums.Exists(c.ReportForumID), "FID #%d should still exist", c.ReportForumID)
   745  	_, err = c.Forums.Get(c.ReportForumID)
   746  	exf(err == nil, "FID #%d should still exist", c.ReportForumID)
   747  	_, err = c.Forums.BypassGet(c.ReportForumID)
   748  	exf(err == nil, "FID #%d should still exist", c.ReportForumID)
   749  
   750  	eforums := map[int]bool{1: true, 2: true}
   751  	{
   752  		forums, err := c.Forums.GetAll()
   753  		expectNilErr(t, err)
   754  		found := make(map[int]*c.Forum)
   755  		for _, forum := range forums {
   756  			_, ok := eforums[forum.ID]
   757  			exf(ok, "unknown forum #%d in forums", forum.ID)
   758  			found[forum.ID] = forum
   759  		}
   760  		for fid, _ := range eforums {
   761  			_, ok := found[fid]
   762  			exf(ok, "unable to find expected forum #%d in forums", fid)
   763  		}
   764  	}
   765  
   766  	{
   767  		fids, err := c.Forums.GetAllIDs()
   768  		expectNilErr(t, err)
   769  		found := make(map[int]bool)
   770  		for _, fid := range fids {
   771  			_, ok := eforums[fid]
   772  			exf(ok, "unknown fid #%d in fids", fid)
   773  			found[fid] = true
   774  		}
   775  		for fid, _ := range eforums {
   776  			_, ok := found[fid]
   777  			exf(ok, "unable to find expected fid #%d in fids", fid)
   778  		}
   779  	}
   780  
   781  	vforums := map[int]bool{2: true}
   782  	{
   783  		forums, err := c.Forums.GetAllVisible()
   784  		expectNilErr(t, err)
   785  		found := make(map[int]*c.Forum)
   786  		for _, forum := range forums {
   787  			_, ok := vforums[forum.ID]
   788  			exf(ok, "unknown forum #%d in forums", forum.ID)
   789  			found[forum.ID] = forum
   790  		}
   791  		for fid, _ := range vforums {
   792  			_, ok := found[fid]
   793  			exf(ok, "unable to find expected forum #%d in forums", fid)
   794  		}
   795  	}
   796  
   797  	{
   798  		fids, err := c.Forums.GetAllVisibleIDs()
   799  		expectNilErr(t, err)
   800  		found := make(map[int]bool)
   801  		for _, fid := range fids {
   802  			_, ok := vforums[fid]
   803  			exf(ok, "unknown fid #%d in fids", fid)
   804  			found[fid] = true
   805  		}
   806  		for fid, _ := range vforums {
   807  			_, ok := found[fid]
   808  			exf(ok, "unable to find expected fid #%d in fids", fid)
   809  		}
   810  	}
   811  
   812  	forum, err = c.Forums.Get(2)
   813  	expectNilErr(t, err)
   814  	prevTopicCount := forum.TopicCount
   815  	tid, err := c.Topics.Create(forum.ID, "Forum Meta Test", "Forum Meta Test", 1, "")
   816  	expectNilErr(t, err)
   817  	forum, err = c.Forums.Get(2)
   818  	expectNilErr(t, err)
   819  	exf(forum.TopicCount == (prevTopicCount+1), "forum.TopicCount should be %d not %d", prevTopicCount+1, forum.TopicCount)
   820  	exf(forum.LastTopicID == tid, "forum.LastTopicID should be %d not %d", tid, forum.LastTopicID)
   821  	exf(forum.LastPage == 1, "forum.LastPage should be %d not %d", 1, forum.LastPage)
   822  
   823  	// TODO: Test topic creation and forum topic metadata
   824  
   825  	// TODO: Test forum update
   826  	// TODO: Other forumstore stuff and forumcache?
   827  }
   828  
   829  // TODO: Implement this
   830  func TestForumPermsStore(t *testing.T) {
   831  	miscinit(t)
   832  	if !c.PluginsInited {
   833  		c.InitPlugins()
   834  	}
   835  	ex := exp(t)
   836  
   837  	f := func(fid, gid int, msg string, inv ...bool) {
   838  		fp, err := c.FPStore.Get(fid, gid)
   839  		if err == ErrNoRows {
   840  			fp = c.BlankForumPerms()
   841  		} else {
   842  			expectNilErr(t, err)
   843  		}
   844  		vt := fp.ViewTopic
   845  		if len(inv) > 0 && inv[0] == true {
   846  			vt = !vt
   847  		}
   848  		ex(vt, msg)
   849  	}
   850  
   851  	// TODO: Test reporting
   852  	initialState := func() {
   853  		f(1, 1, "admins should be able to see reports")
   854  		f(1, 2, "mods should be able to see reports")
   855  		f(1, 3, "members should not be able to see reports", true)
   856  		f(1, 4, "banned users should not be able to see reports", true)
   857  		f(2, 1, "admins should be able to see general")
   858  		f(2, 3, "members should be able to see general")
   859  		f(2, 6, "guests should be able to see general")
   860  	}
   861  	initialState()
   862  
   863  	expectNilErr(t, c.FPStore.Reload(1))
   864  	initialState()
   865  	expectNilErr(t, c.FPStore.Reload(2))
   866  	initialState()
   867  
   868  	gid, err := c.Groups.Create("FP Test", "FP Test", false, false, false)
   869  	expectNilErr(t, err)
   870  	fid, err := c.Forums.Create("FP Test", "FP Test", true, "")
   871  	expectNilErr(t, err)
   872  
   873  	u := c.GuestUser.Copy()
   874  	rt := func(gid, fid int, shouldSucceed bool) {
   875  		w := httptest.NewRecorder()
   876  		bytesBuffer := bytes.NewBuffer([]byte(""))
   877  		sfid := strconv.Itoa(fid)
   878  		req := httptest.NewRequest("", "/forum/"+sfid, bytesBuffer)
   879  		u.Group = gid
   880  		h, err := c.UserCheck(w, req, &u)
   881  		expectNilErr(t, err)
   882  		rerr := routes.ViewForum(w, req, &u, h, sfid)
   883  		if shouldSucceed {
   884  			ex(rerr == nil, "ViewForum should succeed")
   885  		} else {
   886  			ex(rerr != nil, "ViewForum should not succeed")
   887  		}
   888  	}
   889  	rt(1, fid, false)
   890  	rt(2, fid, false)
   891  	rt(3, fid, false)
   892  	rt(4, fid, false)
   893  	rt(gid, fid, false)
   894  
   895  	fp, err := c.FPStore.GetCopy(fid, gid)
   896  	if err == sql.ErrNoRows {
   897  		fp = *c.BlankForumPerms()
   898  	} else if err != nil {
   899  		expectNilErr(t, err)
   900  	}
   901  	fmt.Printf("fp: %+v\n", fp)
   902  
   903  	f(fid, 1, "admins should not be able to see fp test", true)
   904  	f(fid, 2, "mods should not be able to see fp test", true)
   905  	f(fid, 3, "members should not be able to see fp test", true)
   906  	f(fid, 4, "banned users should not be able to see fp test", true)
   907  	f(fid, gid, "fp test should not be able to see fp test", true)
   908  
   909  	fp.ViewTopic = true
   910  
   911  	forum, err := c.Forums.Get(fid)
   912  	expectNilErr(t, err)
   913  	expectNilErr(t, forum.SetPerms(&fp, "custom", gid))
   914  
   915  	rt(1, fid, false)
   916  	rt(2, fid, false)
   917  	rt(3, fid, false)
   918  	rt(4, fid, false)
   919  	rt(gid, fid, true)
   920  
   921  	fp, err = c.FPStore.GetCopy(fid, gid)
   922  	if err == sql.ErrNoRows {
   923  		fp = *c.BlankForumPerms()
   924  	} else if err != nil {
   925  		expectNilErr(t, err)
   926  	}
   927  
   928  	f(fid, 1, "admins should not be able to see fp test", true)
   929  	f(fid, 2, "mods should not be able to see fp test", true)
   930  	f(fid, 3, "members should not be able to see fp test", true)
   931  	f(fid, 4, "banned users should not be able to see fp test", true)
   932  	f(fid, gid, "fp test should be able to see fp test")
   933  
   934  	expectNilErr(t, c.Forums.Delete(fid))
   935  	rt(1, fid, false)
   936  	rt(2, fid, false)
   937  	rt(3, fid, false)
   938  	rt(4, fid, false)
   939  	rt(gid, fid, false)
   940  
   941  	// TODO: Test changing forum permissions
   942  }
   943  
   944  // TODO: Test the group permissions
   945  // TODO: Test group.CanSee for forum presets + group perms
   946  func TestGroupStore(t *testing.T) {
   947  	miscinit(t)
   948  	if !c.PluginsInited {
   949  		c.InitPlugins()
   950  	}
   951  	ex, exf := exp(t), expf(t)
   952  
   953  	_, err := c.Groups.Get(-1)
   954  	recordMustNotExist(t, err, "GID #-1 shouldn't exist")
   955  
   956  	// TODO: Refactor the group store to remove GID #0
   957  	g, err := c.Groups.Get(0)
   958  	recordMustExist(t, err, "Couldn't find GID #0")
   959  	exf(g.ID == 0, "g.ID doesn't not match the requested GID. Got '%d' instead.", g.ID)
   960  	exf(g.Name == "Unknown", "GID #0 is named '%s' and not 'Unknown'", g.Name)
   961  
   962  	g, err = c.Groups.Get(1)
   963  	recordMustExist(t, err, "Couldn't find GID #1")
   964  	exf(g.ID == 1, "g.ID doesn't not match the requested GID. Got '%d' instead.'", g.ID)
   965  	ex(len(g.CanSee) > 0, "g.CanSee should not be zero")
   966  
   967  	ex(!c.Groups.Exists(-1), "GID #-1 shouldn't exist")
   968  	// 0 aka Unknown, for system posts and other oddities
   969  	ex(c.Groups.Exists(0), "GID #0 should exist")
   970  	ex(c.Groups.Exists(1), "GID #1 should exist")
   971  
   972  	isAdmin, isMod, isBanned := true, true, false
   973  	gid, err := c.Groups.Create("Testing", "Test", isAdmin, isMod, isBanned)
   974  	expectNilErr(t, err)
   975  	ex(c.Groups.Exists(gid), "The group we just made doesn't exist")
   976  
   977  	ff := func(i bool) string {
   978  		if !i {
   979  			return "n't"
   980  		}
   981  		return ""
   982  	}
   983  	f := func(gid int, isBanned, isMod, isAdmin bool) {
   984  		ex(g.ID == gid, "The group ID should match the requested ID")
   985  		exf(g.IsAdmin == isAdmin, "This should%s be an admin group", ff(isAdmin))
   986  		exf(g.IsMod == isMod, "This should%s be a mod group", ff(isMod))
   987  		exf(g.IsBanned == isBanned, "This should%s be a ban group", ff(isBanned))
   988  	}
   989  
   990  	g, err = c.Groups.Get(gid)
   991  	expectNilErr(t, err)
   992  	f(gid, false, true, true)
   993  	ex(len(g.CanSee) == 0, "g.CanSee should be empty")
   994  
   995  	isAdmin, isMod, isBanned = false, true, true
   996  	gid, err = c.Groups.Create("Testing 2", "Test", isAdmin, isMod, isBanned)
   997  	expectNilErr(t, err)
   998  	ex(c.Groups.Exists(gid), "The group we just made doesn't exist")
   999  
  1000  	g, err = c.Groups.Get(gid)
  1001  	expectNilErr(t, err)
  1002  	f(gid, false, true, false)
  1003  
  1004  	// TODO: Make sure this pointer doesn't change once we refactor the group store to stop updating the pointer
  1005  	expectNilErr(t, g.ChangeRank(false, false, true))
  1006  
  1007  	g, err = c.Groups.Get(gid)
  1008  	expectNilErr(t, err)
  1009  	f(gid, true, false, false)
  1010  
  1011  	expectNilErr(t, g.ChangeRank(true, true, true))
  1012  
  1013  	g, err = c.Groups.Get(gid)
  1014  	expectNilErr(t, err)
  1015  	f(gid, false, true, true)
  1016  	ex(len(g.CanSee) == 0, "len(g.CanSee) should be 0")
  1017  
  1018  	expectNilErr(t, g.ChangeRank(false, true, true))
  1019  
  1020  	forum, err := c.Forums.Get(2)
  1021  	expectNilErr(t, err)
  1022  	forumPerms, err := c.FPStore.GetCopy(2, gid)
  1023  	if err == sql.ErrNoRows {
  1024  		forumPerms = *c.BlankForumPerms()
  1025  	} else if err != nil {
  1026  		expectNilErr(t, err)
  1027  	}
  1028  	forumPerms.ViewTopic = true
  1029  
  1030  	err = forum.SetPerms(&forumPerms, "custom", gid)
  1031  	expectNilErr(t, err)
  1032  
  1033  	g, err = c.Groups.Get(gid)
  1034  	expectNilErr(t, err)
  1035  	f(gid, false, true, false)
  1036  	ex(g.CanSee != nil, "g.CanSee must not be nil")
  1037  	ex(len(g.CanSee) == 1, "len(g.CanSee) should not be one")
  1038  	ex(g.CanSee[0] == 2, "g.CanSee[0] should be 2")
  1039  	canSee := g.CanSee
  1040  
  1041  	// Make sure the data is static
  1042  	expectNilErr(t, c.Groups.Reload(gid))
  1043  
  1044  	g, err = c.Groups.Get(gid)
  1045  	expectNilErr(t, err)
  1046  	f(gid, false, true, false)
  1047  
  1048  	// TODO: Don't enforce a specific order here
  1049  	canSeeTest := func(a, b []int) bool {
  1050  		if (a == nil) != (b == nil) {
  1051  			return false
  1052  		}
  1053  		if len(a) != len(b) {
  1054  			return false
  1055  		}
  1056  		for i := range a {
  1057  			if a[i] != b[i] {
  1058  				return false
  1059  			}
  1060  		}
  1061  		return true
  1062  	}
  1063  
  1064  	ex(canSeeTest(g.CanSee, canSee), "g.CanSee is not being reused")
  1065  
  1066  	// TODO: Test group deletion
  1067  	// TODO: Test group reload
  1068  	// TODO: Test group cache set
  1069  }
  1070  
  1071  func TestGroupPromotions(t *testing.T) {
  1072  	miscinit(t)
  1073  	if !c.PluginsInited {
  1074  		c.InitPlugins()
  1075  	}
  1076  	ex, exf := exp(t), expf(t)
  1077  
  1078  	_, err := c.GroupPromotions.Get(-1)
  1079  	recordMustNotExist(t, err, "GP #-1 shouldn't exist")
  1080  	_, err = c.GroupPromotions.Get(0)
  1081  	recordMustNotExist(t, err, "GP #0 shouldn't exist")
  1082  	_, err = c.GroupPromotions.Get(1)
  1083  	recordMustNotExist(t, err, "GP #1 shouldn't exist")
  1084  	expectNilErr(t, c.GroupPromotions.Delete(1))
  1085  
  1086  	//GetByGroup(gid int) (gps []*GroupPromotion, err error)
  1087  
  1088  	testPromo := func(exid, from, to, level, posts, registeredFor int, shouldFail bool) {
  1089  		gpid, err := c.GroupPromotions.Create(from, to, false, level, posts, registeredFor)
  1090  		exf(gpid == exid, "gpid should be %d not %d", exid, gpid)
  1091  		//fmt.Println("gpid:", gpid)
  1092  		gp, err := c.GroupPromotions.Get(gpid)
  1093  		expectNilErr(t, err)
  1094  		exf(gp.ID == gpid, "gp.ID should be %d not %d", gpid, gp.ID)
  1095  		exf(gp.From == from, "gp.From should be %d not %d", from, gp.From)
  1096  		exf(gp.To == to, "gp.To should be %d not %d", to, gp.To)
  1097  		ex(!gp.TwoWay, "gp.TwoWay should be false not true")
  1098  		exf(gp.Level == level, "gp.Level should be %d not %d", level, gp.Level)
  1099  		exf(gp.Posts == posts, "gp.Posts should be %d not %d", posts, gp.Posts)
  1100  		exf(gp.MinTime == 0, "gp.MinTime should be %d not %d", 0, gp.MinTime)
  1101  		exf(gp.RegisteredFor == registeredFor, "gp.RegisteredFor should be %d not %d", registeredFor, gp.RegisteredFor)
  1102  
  1103  		uid, err := c.Users.Create("Lord_"+strconv.Itoa(gpid), "I_Rule", "", from, false)
  1104  		expectNilErr(t, err)
  1105  		u, err := c.Users.Get(uid)
  1106  		expectNilErr(t, err)
  1107  		exf(u.ID == uid, "u.ID should be %d not %d", uid, u.ID)
  1108  		exf(u.Group == from, "u.Group should be %d not %d", from, u.Group)
  1109  		err = c.GroupPromotions.PromoteIfEligible(u, u.Level, u.Posts, u.CreatedAt)
  1110  		expectNilErr(t, err)
  1111  		u.CacheRemove()
  1112  		u, err = c.Users.Get(uid)
  1113  		expectNilErr(t, err)
  1114  		exf(u.ID == uid, "u.ID should be %d not %d", uid, u.ID)
  1115  		if shouldFail {
  1116  			exf(u.Group == from, "u.Group should be (from-group) %d not %d", from, u.Group)
  1117  		} else {
  1118  			exf(u.Group == to, "u.Group should be (to-group)%d not %d", to, u.Group)
  1119  		}
  1120  
  1121  		expectNilErr(t, c.GroupPromotions.Delete(gpid))
  1122  		_, err = c.GroupPromotions.Get(gpid)
  1123  		recordMustNotExist(t, err, fmt.Sprintf("GP #%d should no longer exist", gpid))
  1124  	}
  1125  	testPromo(1, 1, 2, 0, 0, 0, false)
  1126  	testPromo(2, 1, 2, 5, 5, 0, true)
  1127  	testPromo(3, 1, 2, 0, 0, 1, true)
  1128  }
  1129  
  1130  func TestReplyStore(t *testing.T) {
  1131  	miscinit(t)
  1132  	if !c.PluginsInited {
  1133  		c.InitPlugins()
  1134  	}
  1135  	_, e := c.Rstore.Get(-1)
  1136  	recordMustNotExist(t, e, "RID #-1 shouldn't exist")
  1137  	_, e = c.Rstore.Get(0)
  1138  	recordMustNotExist(t, e, "RID #0 shouldn't exist")
  1139  
  1140  	c.Config.DisablePostIP = false
  1141  	testReplyStore(t, 2, "::1")
  1142  	c.Config.DisablePostIP = true
  1143  	testReplyStore(t, 5, "")
  1144  }
  1145  
  1146  func testReplyStore(t *testing.T, newID int, ip string) {
  1147  	ex, exf := exp(t), expf(t)
  1148  	replyTest2 := func(r *c.Reply, e error, rid, parentID, createdBy int, content, ip string) {
  1149  		expectNilErr(t, e)
  1150  		exf(r.ID == rid, "RID #%d has the wrong ID. It should be %d not %d", rid, rid, r.ID)
  1151  		exf(r.ParentID == parentID, "The parent topic of RID #%d should be %d not %d", rid, parentID, r.ParentID)
  1152  		exf(r.CreatedBy == createdBy, "The creator of RID #%d should be %d not %d", rid, createdBy, r.CreatedBy)
  1153  		exf(r.Content == content, "The contents of RID #%d should be '%s' not %s", rid, content, r.Content)
  1154  		exf(r.IP == ip, "The IP of RID#%d should be '%s' not %s", rid, ip, r.IP)
  1155  	}
  1156  
  1157  	replyTest := func(rid, parentID, createdBy int, content, ip string) {
  1158  		r, e := c.Rstore.Get(rid)
  1159  		replyTest2(r, e, rid, parentID, createdBy, content, ip)
  1160  		r, e = c.Rstore.GetCache().Get(rid)
  1161  		replyTest2(r, e, rid, parentID, createdBy, content, ip)
  1162  	}
  1163  	replyTest(1, 1, 1, "A reply!", "")
  1164  
  1165  	// ! This is hard to do deterministically as the system may pre-load certain items but let's give it a try:
  1166  	//_, err = c.Rstore.GetCache().Get(1)
  1167  	//recordMustNotExist(t, err, "RID #1 shouldn't be in the cache")
  1168  
  1169  	_, err := c.Rstore.Get(newID)
  1170  	recordMustNotExist(t, err, "RID #2 shouldn't exist")
  1171  
  1172  	newPostCount := 1
  1173  	tid, err := c.Topics.Create(2, "Reply Test Topic", "Reply Test Topic", 1, "")
  1174  	expectNilErr(t, err)
  1175  
  1176  	topic, err := c.Topics.Get(tid)
  1177  	expectNilErr(t, err)
  1178  	exf(topic.PostCount == newPostCount, "topic.PostCount should be %d, not %d", newPostCount, topic.PostCount)
  1179  	exf(topic.LastReplyID == 0, "topic.LastReplyID should be %d not %d", 0, topic.LastReplyID)
  1180  	ex(topic.CreatedAt == topic.LastReplyAt, "topic.LastReplyAt should equal it's topic.CreatedAt")
  1181  	exf(topic.LastReplyBy == 1, "topic.LastReplyBy should be %d not %d", 1, topic.LastReplyBy)
  1182  
  1183  	_, err = c.Rstore.GetCache().Get(newID)
  1184  	recordMustNotExist(t, err, "RID #%d shouldn't be in the cache", newID)
  1185  
  1186  	time.Sleep(2 * time.Second)
  1187  
  1188  	uid, err := c.Users.Create("Reply Topic Test User"+strconv.Itoa(newID), "testpassword", "", 2, true)
  1189  	expectNilErr(t, err)
  1190  	rid, err := c.Rstore.Create(topic, "Fofofo", ip, uid)
  1191  	expectNilErr(t, err)
  1192  	exf(rid == newID, "The next reply ID should be %d not %d", newID, rid)
  1193  	exf(topic.PostCount == newPostCount, "The old topic in memory's post count should be %d, not %d", newPostCount+1, topic.PostCount)
  1194  	// TODO: Test the reply count on the topic
  1195  	exf(topic.LastReplyID == 0, "topic.LastReplyID should be %d not %d", 0, topic.LastReplyID)
  1196  	ex(topic.CreatedAt == topic.LastReplyAt, "topic.LastReplyAt should equal it's topic.CreatedAt")
  1197  
  1198  	replyTest(newID, tid, uid, "Fofofo", ip)
  1199  
  1200  	topic, err = c.Topics.Get(tid)
  1201  	expectNilErr(t, err)
  1202  	exf(topic.PostCount == newPostCount+1, "topic.PostCount should be %d, not %d", newPostCount+1, topic.PostCount)
  1203  	exf(topic.LastReplyID == rid, "topic.LastReplyID should be %d not %d", rid, topic.LastReplyID)
  1204  	ex(topic.CreatedAt != topic.LastReplyAt, "topic.LastReplyAt should not equal it's topic.CreatedAt")
  1205  	exf(topic.LastReplyBy == uid, "topic.LastReplyBy should be %d not %d", uid, topic.LastReplyBy)
  1206  
  1207  	expectNilErr(t, topic.CreateActionReply("destroy", ip, 1))
  1208  	exf(topic.PostCount == newPostCount+1, "The old topic in memory's post count should be %d, not %d", newPostCount+1, topic.PostCount)
  1209  	replyTest(newID+1, tid, 1, "", ip)
  1210  	// TODO: Check the actionType field of the reply, this might not be loaded by TopicStore, maybe we should add it there?
  1211  
  1212  	topic, err = c.Topics.Get(tid)
  1213  	expectNilErr(t, err)
  1214  	exf(topic.PostCount == newPostCount+2, "topic.PostCount should be %d, not %d", newPostCount+2, topic.PostCount)
  1215  	exf(topic.LastReplyID != rid, "topic.LastReplyID should not be %d", rid)
  1216  	arid := topic.LastReplyID
  1217  
  1218  	// TODO: Expand upon this
  1219  	rid, err = c.Rstore.Create(topic, "hiii", ip, 1)
  1220  	expectNilErr(t, err)
  1221  	replyTest(rid, topic.ID, 1, "hiii", ip)
  1222  
  1223  	reply, err := c.Rstore.Get(rid)
  1224  	expectNilErr(t, err)
  1225  	expectNilErr(t, reply.SetPost("huuu"))
  1226  	exf(reply.Content == "hiii", "topic.Content should be hiii, not %s", reply.Content)
  1227  
  1228  	reply, err = c.Rstore.Get(rid)
  1229  	replyTest2(reply, err, rid, topic.ID, 1, "huuu", ip)
  1230  	expectNilErr(t, c.Rstore.ClearIPs())
  1231  	_ = c.Rstore.GetCache().Remove(rid)
  1232  	replyTest(rid, topic.ID, 1, "huuu", "")
  1233  
  1234  	expectNilErr(t, reply.Delete())
  1235  	// No pointer shenanigans x.x
  1236  	// TODO: Log reply.ID and rid in cases of pointer shenanigans?
  1237  	ex(reply.ID == rid, "pointer shenanigans")
  1238  
  1239  	_, err = c.Rstore.GetCache().Get(rid)
  1240  	recordMustNotExist(t, err, fmt.Sprintf("RID #%d shouldn't be in the cache", rid))
  1241  	_, err = c.Rstore.Get(rid)
  1242  	recordMustNotExist(t, err, fmt.Sprintf("RID #%d shouldn't exist", rid))
  1243  
  1244  	topic, err = c.Topics.Get(tid)
  1245  	expectNilErr(t, err)
  1246  	exf(topic.LastReplyID == arid, "topic.LastReplyID should be %d not %d", arid, topic.LastReplyID)
  1247  
  1248  	// TODO: Write a test for this
  1249  	//(topic *TopicUser) Replies(offset int, pFrag int, user *User) (rlist []*ReplyUser, ogdesc string, err error)
  1250  
  1251  	// TODO: Add tests for *Reply
  1252  	// TODO: Add tests for ReplyCache
  1253  }
  1254  
  1255  func TestLikes(t *testing.T) {
  1256  	miscinit(t)
  1257  	if !c.PluginsInited {
  1258  		c.InitPlugins()
  1259  	}
  1260  	_, exf := exp(t), expf(t)
  1261  	bulkExists := func(iids []int, sentBy int, targetType string, expCount int) {
  1262  		ids, e := c.Likes.BulkExists(iids, sentBy, "replies")
  1263  		//recordMustNotExist(t, e, "no likes should be found")
  1264  		expectNilErr(t, e)
  1265  		exf(len(ids) == expCount, "len ids should be %d", expCount)
  1266  
  1267  		idMap := make(map[int]struct{})
  1268  		for _, id := range ids {
  1269  			idMap[id] = struct{}{}
  1270  		}
  1271  		for _, iid := range iids {
  1272  			_, ok := idMap[iid]
  1273  			exf(ok, "missing iid %d in idMap", iid)
  1274  		}
  1275  
  1276  		idCount := 0
  1277  		expectNilErr(t, c.Likes.BulkExistsFunc(iids, sentBy, targetType, func(_ int) error {
  1278  			idCount++
  1279  			return nil
  1280  		}))
  1281  		exf(idCount == expCount, "idCount should be %d not %d", expCount, idCount)
  1282  	}
  1283  
  1284  	uid := 1
  1285  	bulkExists([]int{}, uid, "replies", 0)
  1286  
  1287  	topic, e := c.Topics.Get(1)
  1288  	expectNilErr(t, e)
  1289  	rid, e := c.Rstore.Create(topic, "hiii", "", uid)
  1290  	expectNilErr(t, e)
  1291  	r, e := c.Rstore.Get(rid)
  1292  	expectNilErr(t, e)
  1293  	expectNilErr(t, r.Like(uid))
  1294  	bulkExists([]int{rid}, uid, "replies", 1)
  1295  
  1296  	rid2, e := c.Rstore.Create(topic, "hi 2 u 2", "", uid)
  1297  	expectNilErr(t, e)
  1298  	r2, e := c.Rstore.Get(rid2)
  1299  	expectNilErr(t, e)
  1300  	expectNilErr(t, r2.Like(uid))
  1301  	bulkExists([]int{rid, rid2}, uid, "replies", 2)
  1302  
  1303  	expectNilErr(t, r.Unlike(uid))
  1304  	bulkExists([]int{rid2}, uid, "replies", 1)
  1305  	expectNilErr(t, r2.Unlike(uid))
  1306  	bulkExists([]int{}, uid, "replies", 0)
  1307  
  1308  	//BulkExists(ids []int, sentBy int, targetType string) (eids []int, err error)
  1309  
  1310  	expectNilErr(t, topic.Like(1, uid))
  1311  	expectNilErr(t, topic.Unlike(uid))
  1312  }
  1313  
  1314  func TestAttachments(t *testing.T) {
  1315  	miscinit(t)
  1316  	if !c.PluginsInited {
  1317  		c.InitPlugins()
  1318  	}
  1319  	ex, exf := exp(t), expf(t)
  1320  
  1321  	filename := "n0-48.png"
  1322  	srcFile := "./test_data/" + filename
  1323  	destFile := "./attachs/" + filename
  1324  
  1325  	ft := func(e error) {
  1326  		if e != nil && e != sql.ErrNoRows {
  1327  			t.Error(e)
  1328  		}
  1329  	}
  1330  
  1331  	ex(c.Attachments.Count() == 0, "the number of attachments should be 0")
  1332  	ex(c.Attachments.CountIn("topics", 1) == 0, "the number of attachments in topic 1 should be 0")
  1333  	exf(c.Attachments.CountInPath(filename) == 0, "the number of attachments with path '%s' should be 0", filename)
  1334  	_, e := c.Attachments.FGet(1)
  1335  	ft(e)
  1336  	ex(e == sql.ErrNoRows, ".FGet should have no results")
  1337  	_, e = c.Attachments.Get(1)
  1338  	ft(e)
  1339  	ex(e == sql.ErrNoRows, ".Get should have no results")
  1340  	_, e = c.Attachments.MiniGetList("topics", 1)
  1341  	ft(e)
  1342  	ex(e == sql.ErrNoRows, ".MiniGetList should have no results")
  1343  	_, e = c.Attachments.BulkMiniGetList("topics", []int{1})
  1344  	ft(e)
  1345  	ex(e == sql.ErrNoRows, ".BulkMiniGetList should have no results")
  1346  
  1347  	simUpload := func() {
  1348  		// Sim an upload, try a proper upload through the proper pathway later on
  1349  		_, e = os.Stat(destFile)
  1350  		if e != nil && !os.IsNotExist(e) {
  1351  			expectNilErr(t, e)
  1352  		} else if e == nil {
  1353  			expectNilErr(t, os.Remove(destFile))
  1354  		}
  1355  
  1356  		input, e := ioutil.ReadFile(srcFile)
  1357  		expectNilErr(t, e)
  1358  		expectNilErr(t, ioutil.WriteFile(destFile, input, 0644))
  1359  	}
  1360  	simUpload()
  1361  
  1362  	tid, e := c.Topics.Create(2, "Attach Test", "Filler Body", 1, "")
  1363  	expectNilErr(t, e)
  1364  	aid, e := c.Attachments.Add(2, "forums", tid, "topics", 1, filename, "")
  1365  	expectNilErr(t, e)
  1366  	exf(aid == 1, "aid should be 1 not %d", aid)
  1367  	expectNilErr(t, c.Attachments.AddLinked("topics", tid))
  1368  	ex(c.Attachments.Count() == 1, "the number of attachments should be 1")
  1369  	exf(c.Attachments.CountIn("topics", tid) == 1, "the number of attachments in topic %d should be 1", tid)
  1370  	exf(c.Attachments.CountInPath(filename) == 1, "the number of attachments with path '%s' should be 1", filename)
  1371  
  1372  	et := func(a *c.MiniAttachment, aid, sid, oid, uploadedBy int, path, extra, ext string) {
  1373  		exf(a.ID == aid, "ID should be %d not %d", aid, a.ID)
  1374  		exf(a.SectionID == sid, "SectionID should be %d not %d", sid, a.SectionID)
  1375  		exf(a.OriginID == oid, "OriginID should be %d not %d", oid, a.OriginID)
  1376  		exf(a.UploadedBy == uploadedBy, "UploadedBy should be %d not %d", uploadedBy, a.UploadedBy)
  1377  		exf(a.Path == path, "Path should be %s not %s", path, a.Path)
  1378  		exf(a.Extra == extra, "Extra should be %s not %s", extra, a.Extra)
  1379  		ex(a.Image, "Image should be true")
  1380  		exf(a.Ext == ext, "Ext should be %s not %s", ext, a.Ext)
  1381  	}
  1382  	et2 := func(a *c.Attachment, aid, sid, oid, uploadedBy int, path, extra, ext string) {
  1383  		exf(a.ID == aid, "ID should be %d not %d", aid, a.ID)
  1384  		exf(a.SectionID == sid, "SectionID should be %d not %d", sid, a.SectionID)
  1385  		exf(a.OriginID == oid, "OriginID should be %d not %d", oid, a.OriginID)
  1386  		exf(a.UploadedBy == uploadedBy, "UploadedBy should be %d not %d", uploadedBy, a.UploadedBy)
  1387  		exf(a.Path == path, "Path should be %s not %s", path, a.Path)
  1388  		exf(a.Extra == extra, "Extra should be %s not %s", extra, a.Extra)
  1389  		ex(a.Image, "Image should be true")
  1390  		exf(a.Ext == ext, "Ext should be %s not %s", ext, a.Ext)
  1391  	}
  1392  
  1393  	f2 := func(aid, sid, oid int, extra string, topic bool) {
  1394  		var tbl string
  1395  		if topic {
  1396  			tbl = "topics"
  1397  		} else {
  1398  			tbl = "replies"
  1399  		}
  1400  		fa, e := c.Attachments.FGet(aid)
  1401  		expectNilErr(t, e)
  1402  		et2(fa, aid, sid, oid, 1, filename, extra, "png")
  1403  
  1404  		a, e := c.Attachments.Get(aid)
  1405  		expectNilErr(t, e)
  1406  		et(a, aid, sid, oid, 1, filename, extra, "png")
  1407  
  1408  		alist, e := c.Attachments.MiniGetList(tbl, oid)
  1409  		expectNilErr(t, e)
  1410  		exf(len(alist) == 1, "len(alist) should be 1 not %d", len(alist))
  1411  		a = alist[0]
  1412  		et(a, aid, sid, oid, 1, filename, extra, "png")
  1413  
  1414  		amap, e := c.Attachments.BulkMiniGetList(tbl, []int{oid})
  1415  		expectNilErr(t, e)
  1416  		exf(len(amap) == 1, "len(amap) should be 1 not %d", len(amap))
  1417  		alist, ok := amap[oid]
  1418  		if !ok {
  1419  			t.Logf("key %d not found in amap", oid)
  1420  		}
  1421  		exf(len(alist) == 1, "len(alist) should be 1 not %d", len(alist))
  1422  		a = alist[0]
  1423  		et(a, aid, sid, oid, 1, filename, extra, "png")
  1424  	}
  1425  
  1426  	topic, e := c.Topics.Get(tid)
  1427  	expectNilErr(t, e)
  1428  	exf(topic.AttachCount == 1, "topic.AttachCount should be 1 not %d", topic.AttachCount)
  1429  	f2(aid, 2, tid, "", true)
  1430  	expectNilErr(t, topic.MoveTo(1))
  1431  	f2(aid, 1, tid, "", true)
  1432  	expectNilErr(t, c.Attachments.MoveTo(2, tid, "topics"))
  1433  	f2(aid, 2, tid, "", true)
  1434  
  1435  	// TODO: ShowAttachment test
  1436  
  1437  	deleteTest := func(aid, oid int, topic bool) {
  1438  		var tbl string
  1439  		if topic {
  1440  			tbl = "topics"
  1441  		} else {
  1442  			tbl = "replies"
  1443  		}
  1444  		//expectNilErr(t, c.Attachments.Delete(aid))
  1445  		expectNilErr(t, c.DeleteAttachment(aid))
  1446  		ex(c.Attachments.Count() == 0, "the number of attachments should be 0")
  1447  		exf(c.Attachments.CountIn(tbl, oid) == 0, "the number of attachments in topic %d should be 0", tid)
  1448  		exf(c.Attachments.CountInPath(filename) == 0, "the number of attachments with path '%s' should be 0", filename)
  1449  		_, e = c.Attachments.FGet(aid)
  1450  		ft(e)
  1451  		ex(e == sql.ErrNoRows, ".FGet should have no results")
  1452  		_, e = c.Attachments.Get(aid)
  1453  		ft(e)
  1454  		ex(e == sql.ErrNoRows, ".Get should have no results")
  1455  		_, e = c.Attachments.MiniGetList(tbl, oid)
  1456  		ft(e)
  1457  		ex(e == sql.ErrNoRows, ".MiniGetList should have no results")
  1458  		_, e = c.Attachments.BulkMiniGetList(tbl, []int{oid})
  1459  		ft(e)
  1460  		ex(e == sql.ErrNoRows, ".BulkMiniGetList should have no results")
  1461  	}
  1462  	deleteTest(aid, tid, true)
  1463  	topic, e = c.Topics.Get(tid)
  1464  	expectNilErr(t, e)
  1465  	exf(topic.AttachCount == 0, "topic.AttachCount should be 0 not %d", topic.AttachCount)
  1466  
  1467  	simUpload()
  1468  	rid, e := c.Rstore.Create(topic, "Reply Filler", "", 1)
  1469  	expectNilErr(t, e)
  1470  	aid, e = c.Attachments.Add(2, "forums", rid, "replies", 1, filename, strconv.Itoa(topic.ID))
  1471  	expectNilErr(t, e)
  1472  	exf(aid == 2, "aid should be 2 not %d", aid)
  1473  	expectNilErr(t, c.Attachments.AddLinked("replies", rid))
  1474  	r, e := c.Rstore.Get(rid)
  1475  	expectNilErr(t, e)
  1476  	exf(r.AttachCount == 1, "r.AttachCount should be 1 not %d", r.AttachCount)
  1477  	f2(aid, 2, rid, strconv.Itoa(topic.ID), false)
  1478  	expectNilErr(t, c.Attachments.MoveTo(1, rid, "replies"))
  1479  	f2(aid, 1, rid, strconv.Itoa(topic.ID), false)
  1480  	deleteTest(aid, rid, false)
  1481  	r, e = c.Rstore.Get(rid)
  1482  	expectNilErr(t, e)
  1483  	exf(r.AttachCount == 0, "r.AttachCount should be 0 not %d", r.AttachCount)
  1484  
  1485  	// TODO: Path overlap tests
  1486  }
  1487  
  1488  func TestPolls(t *testing.T) {
  1489  	miscinit(t)
  1490  	if !c.PluginsInited {
  1491  		c.InitPlugins()
  1492  	}
  1493  	ex, exf := exp(t), expf(t)
  1494  
  1495  	shouldNotExist := func(id int) {
  1496  		exf(!c.Polls.Exists(id), "poll %d should not exist", id)
  1497  		_, e := c.Polls.Get(id)
  1498  		recordMustNotExist(t, e, fmt.Sprintf("poll %d shouldn't exist", id))
  1499  	}
  1500  	shouldNotExist(-1)
  1501  	shouldNotExist(0)
  1502  	shouldNotExist(1)
  1503  	exf(c.Polls.Count() == 0, "count should be %d not %d", 0, c.Polls.Count())
  1504  
  1505  	tid, e := c.Topics.Create(2, "Poll Test", "Filler Body", 1, "")
  1506  	expectNilErr(t, e)
  1507  	topic, e := c.Topics.Get(tid)
  1508  	expectNilErr(t, e)
  1509  	exf(topic.Poll == 0, "t.Poll should be %d not %d", 0, topic.Poll)
  1510  	/*Options      map[int]string
  1511  		Results      map[int]int  // map[optionIndex]points
  1512  		QuickOptions []PollOption // TODO: Fix up the template transpiler so we don't need to use this hack anymore
  1513  	}*/
  1514  	pollType := 0 // Basic single choice
  1515  	pid, e := c.Polls.Create(topic, pollType, map[int]string{0: "item 1", 1: "item 2", 2: "item 3"})
  1516  	expectNilErr(t, e)
  1517  	exf(pid == 1, "poll id should be 1 not %d", pid)
  1518  	ex(c.Polls.Exists(1), "poll 1 should exist")
  1519  	exf(c.Polls.Count() == 1, "count should be %d not %d", 1, c.Polls.Count())
  1520  	topic, e = c.Topics.BypassGet(tid)
  1521  	expectNilErr(t, e)
  1522  	exf(topic.Poll == pid, "t.Poll should be %d not %d", pid, topic.Poll)
  1523  
  1524  	testPoll := func(p *c.Poll, id, parentID int, parentTable string, ptype int, antiCheat bool, voteCount int) {
  1525  		ef := exf
  1526  		ef(p.ID == id, "p.ID should be %d not %d", id, p.ID)
  1527  		ef(p.ParentID == parentID, "p.ParentID should be %d not %d", parentID, p.ParentID)
  1528  		ef(p.ParentTable == parentTable, "p.ParentID should be %s not %s", parentTable, p.ParentTable)
  1529  		ef(p.Type == ptype, "p.ParentID should be %d not %d", ptype, p.Type)
  1530  		s := "false"
  1531  		if p.AntiCheat {
  1532  			s = "true"
  1533  		}
  1534  		ef(p.AntiCheat == antiCheat, "p.AntiCheat should be ", s)
  1535  		// TODO: More fields
  1536  		ef(p.VoteCount == voteCount, "p.VoteCount should be %d not %d", voteCount, p.VoteCount)
  1537  	}
  1538  
  1539  	p, e := c.Polls.Get(1)
  1540  	expectNilErr(t, e)
  1541  	testPoll(p, 1, tid, "topics", 0, false, 0)
  1542  
  1543  	expectNilErr(t, p.CastVote(0, 1, ""))
  1544  	expectNilErr(t, c.Polls.Reload(p.ID))
  1545  	p, e = c.Polls.Get(1)
  1546  	expectNilErr(t, e)
  1547  	testPoll(p, 1, tid, "topics", 0, false, 1)
  1548  
  1549  	var vslice []int
  1550  	expectNilErr(t, p.Resultsf(func(votes int) error {
  1551  		vslice = append(vslice, votes)
  1552  		return nil
  1553  	}))
  1554  	//fmt.Printf("vslice: %+v\n", vslice)
  1555  	exf(vslice[0] == 1, "vslice[0] should be %d not %d", 0, vslice[0])
  1556  	exf(vslice[1] == 0, "vslice[1] should be %d not %d", 1, vslice[1])
  1557  	exf(vslice[2] == 0, "vslice[2] should be %d not %d", 0, vslice[2])
  1558  
  1559  	expectNilErr(t, p.CastVote(2, 1, ""))
  1560  	expectNilErr(t, c.Polls.Reload(p.ID))
  1561  	p, e = c.Polls.Get(1)
  1562  	expectNilErr(t, e)
  1563  	testPoll(p, 1, tid, "topics", 0, false, 2)
  1564  
  1565  	vslice = nil
  1566  	expectNilErr(t, p.Resultsf(func(votes int) error {
  1567  		vslice = append(vslice, votes)
  1568  		return nil
  1569  	}))
  1570  	//fmt.Printf("vslice: %+v\n", vslice)
  1571  	exf(vslice[0] == 1, "vslice[0] should be %d not %d", 1, vslice[0])
  1572  	exf(vslice[1] == 0, "vslice[1] should be %d not %d", 0, vslice[1])
  1573  	exf(vslice[2] == 1, "vslice[2] should be %d not %d", 1, vslice[2])
  1574  
  1575  	expectNilErr(t, c.Polls.ClearIPs())
  1576  	// TODO: Test to see if it worked
  1577  
  1578  	expectNilErr(t, p.Delete())
  1579  	ex(!c.Polls.Exists(1), "poll 1 should no longer exist")
  1580  	_, e = c.Polls.Get(1)
  1581  	recordMustNotExist(t, e, "poll 1 should no longer exist")
  1582  	exf(c.Polls.Count() == 0, "count should be %d not %d", 0, c.Polls.Count())
  1583  	topic, e = c.Topics.BypassGet(tid)
  1584  	expectNilErr(t, e)
  1585  	exf(topic.Poll == pid, "t.Poll should be %d not %d", pid, topic.Poll)
  1586  
  1587  	expectNilErr(t, topic.SetPoll(999))
  1588  	topic, e = c.Topics.BypassGet(tid)
  1589  	expectNilErr(t, e)
  1590  	exf(topic.Poll == pid, "t.Poll should be %d not %d", pid, topic.Poll)
  1591  
  1592  	expectNilErr(t, topic.SetPoll(0))
  1593  	topic, e = c.Topics.BypassGet(tid)
  1594  	expectNilErr(t, e)
  1595  	exf(topic.Poll == pid, "t.Poll should be %d not %d", pid, topic.Poll)
  1596  
  1597  	expectNilErr(t, topic.RemovePoll())
  1598  	topic, e = c.Topics.BypassGet(tid)
  1599  	expectNilErr(t, e)
  1600  	exf(topic.Poll == 0, "t.Poll should be %d not %d", 0, topic.Poll)
  1601  }
  1602  
  1603  func TestSearch(t *testing.T) {
  1604  	miscinit(t)
  1605  	if !c.PluginsInited {
  1606  		c.InitPlugins()
  1607  	}
  1608  	exf := expf(t)
  1609  
  1610  	title := "search"
  1611  	body := "bab bab bab bab"
  1612  	q := "search"
  1613  	tid, e := c.Topics.Create(2, title, body, 1, "")
  1614  	expectNilErr(t, e)
  1615  
  1616  	tids, e := c.RepliesSearch.Query(q, []int{2})
  1617  	//fmt.Printf("tids: %+v\n", tids)
  1618  	expectNilErr(t, e)
  1619  	exf(len(tids) == 1, "len(tids) should be 1 not %d", len(tids))
  1620  
  1621  	topic, e := c.Topics.Get(tids[0])
  1622  	expectNilErr(t, e)
  1623  	exf(topic.ID == tid, "topic.ID should be %d not %d", tid, topic.ID)
  1624  	exf(topic.Title == title, "topic.Title should be %s not %s", title, topic.Title)
  1625  
  1626  	tids, e = c.RepliesSearch.Query(q, []int{1, 2})
  1627  	//fmt.Printf("tids: %+v\n", tids)
  1628  	expectNilErr(t, e)
  1629  	exf(len(tids) == 1, "len(tids) should be 1 not %d", len(tids))
  1630  
  1631  	q = "bab"
  1632  	tids, e = c.RepliesSearch.Query(q, []int{1, 2})
  1633  	//fmt.Printf("tids: %+v\n", tids)
  1634  	expectNilErr(t, e)
  1635  	exf(len(tids) == 1, "len(tids) should be 1 not %d", len(tids))
  1636  }
  1637  
  1638  func TestProfileReplyStore(t *testing.T) {
  1639  	miscinit(t)
  1640  	if !c.PluginsInited {
  1641  		c.InitPlugins()
  1642  	}
  1643  
  1644  	_, e := c.Prstore.Get(-1)
  1645  	recordMustNotExist(t, e, "PRID #-1 shouldn't exist")
  1646  	_, e = c.Prstore.Get(0)
  1647  	recordMustNotExist(t, e, "PRID #0 shouldn't exist")
  1648  	_, e = c.Prstore.Get(1)
  1649  	recordMustNotExist(t, e, "PRID #1 shouldn't exist")
  1650  
  1651  	c.Config.DisablePostIP = false
  1652  	testProfileReplyStore(t, 1, "::1")
  1653  	c.Config.DisablePostIP = true
  1654  	testProfileReplyStore(t, 2, "")
  1655  }
  1656  func testProfileReplyStore(t *testing.T, newID int, ip string) {
  1657  	exf := expf(t)
  1658  	// ? - Commented this one out as strong constraints like this put an unreasonable load on the database, we only want errors if a delete which should succeed fails
  1659  	//profileReply := c.BlankProfileReply(1)
  1660  	//e = profileReply.Delete()
  1661  	//expect(t,e != nil,"You shouldn't be able to delete profile replies which don't exist")
  1662  
  1663  	profileID := 1
  1664  	prid, e := c.Prstore.Create(profileID, "Haha", 1, ip)
  1665  	expectNilErr(t, e)
  1666  	exf(prid == newID, "The first profile reply should have an ID of %d", newID)
  1667  
  1668  	pr, e := c.Prstore.Get(newID)
  1669  	expectNilErr(t, e)
  1670  	exf(pr.ID == newID, "The profile reply should have an ID of %d not %d", newID, pr.ID)
  1671  	exf(pr.ParentID == 1, "The parent ID of the profile reply should be 1 not %d", pr.ParentID)
  1672  	exf(pr.Content == "Haha", "The profile reply's contents should be 'Haha' not '%s'", pr.Content)
  1673  	exf(pr.CreatedBy == 1, "The profile reply's creator should be 1 not %d", pr.CreatedBy)
  1674  	exf(pr.IP == ip, "The profile reply's IP should be '%s' not '%s'", ip, pr.IP)
  1675  
  1676  	expectNilErr(t, c.Prstore.ClearIPs())
  1677  
  1678  	pr, e = c.Prstore.Get(newID)
  1679  	expectNilErr(t, e)
  1680  	exf(pr.ID == newID, "The profile reply should have an ID of %d not %d", newID, pr.ID)
  1681  	exf(pr.ParentID == 1, "The parent ID of the profile reply should be 1 not %d", pr.ParentID)
  1682  	exf(pr.Content == "Haha", "The profile reply's contents should be 'Haha' not '%s'", pr.Content)
  1683  	exf(pr.CreatedBy == 1, "The profile reply's creator should be 1 not %d", pr.CreatedBy)
  1684  	ip = ""
  1685  	exf(pr.IP == ip, "The profile reply's IP should be '%s' not '%s'", ip, pr.IP)
  1686  
  1687  	expectNilErr(t, pr.Delete())
  1688  	_, e = c.Prstore.Get(newID)
  1689  	exf(e != nil, "PRID #%d shouldn't exist after being deleted", newID)
  1690  
  1691  	// TODO: Test pr.SetBody() and pr.Creator()
  1692  }
  1693  
  1694  func TestConvos(t *testing.T) {
  1695  	miscinit(t)
  1696  	if !c.PluginsInited {
  1697  		c.InitPlugins()
  1698  	}
  1699  	ex, exf := exp(t), expf(t)
  1700  
  1701  	sf := func(i interface{}, e error) error {
  1702  		return e
  1703  	}
  1704  	mf := func(e error, msg string, exists bool) {
  1705  		if !exists {
  1706  			recordMustNotExist(t, e, msg)
  1707  		} else {
  1708  			recordMustExist(t, e, msg)
  1709  		}
  1710  	}
  1711  	gu := func(uid, offset int, exists bool) {
  1712  		s := ""
  1713  		if !exists {
  1714  			s = " not"
  1715  		}
  1716  		mf(sf(c.Convos.GetUser(uid, offset)), fmt.Sprintf("convo getuser %d %d should%s exist", uid, offset, s), exists)
  1717  	}
  1718  	gue := func(uid, offset int, exists bool) {
  1719  		s := ""
  1720  		if !exists {
  1721  			s = " not"
  1722  		}
  1723  		mf(sf(c.Convos.GetUserExtra(uid, offset)), fmt.Sprintf("convo getuserextra %d %d should%s exist", uid, offset, s), exists)
  1724  	}
  1725  
  1726  	ex(c.Convos.GetUserCount(-1) == 0, "getusercount should be 0")
  1727  	ex(c.Convos.GetUserCount(0) == 0, "getusercount should be 0")
  1728  	mf(sf(c.Convos.Get(-1)), "convo -1 should not exist", false)
  1729  	mf(sf(c.Convos.Get(0)), "convo 0 should not exist", false)
  1730  	gu(-1, -1, false)
  1731  	gu(-1, 0, false)
  1732  	gu(0, 0, false)
  1733  	gue(-1, -1, false)
  1734  	gue(-1, 0, false)
  1735  	gue(0, 0, false)
  1736  
  1737  	nf := func(cid, count int) {
  1738  		ex := count > 0
  1739  		s := ""
  1740  		if !ex {
  1741  			s = " not"
  1742  		}
  1743  		mf(sf(c.Convos.Get(cid)), fmt.Sprintf("convo %d should%s exist", cid, s), ex)
  1744  		gu(1, 0, ex)
  1745  		gu(1, 5, false) // invariant may change in future tests
  1746  
  1747  		exf(c.Convos.GetUserCount(1) == count, "getusercount should be %d", count)
  1748  		gue(1, 0, ex)
  1749  		gue(1, 5, false) // invariant may change in future tests
  1750  		exf(c.Convos.Count() == count, "convos count should be %d", count)
  1751  	}
  1752  	nf(1, 0)
  1753  
  1754  	awaitingActivation := 5
  1755  	uid, err := c.Users.Create("Saturn", "ReallyBadPassword", "", awaitingActivation, false)
  1756  	expectNilErr(t, err)
  1757  
  1758  	cid, err := c.Convos.Create("hehe", 1, []int{uid})
  1759  	expectNilErr(t, err)
  1760  	ex(cid == 1, "cid should be 1")
  1761  	ex(c.Convos.Count() == 1, "convos count should be 1")
  1762  
  1763  	co, err := c.Convos.Get(cid)
  1764  	expectNilErr(t, err)
  1765  	ex(co.ID == 1, "co.ID should be 1")
  1766  	ex(co.CreatedBy == 1, "co.CreatedBy should be 1")
  1767  	// TODO: CreatedAt test
  1768  	ex(co.LastReplyBy == 1, "co.LastReplyBy should be 1")
  1769  	// TODO: LastReplyAt test
  1770  	expectIntToBeX(t, co.PostsCount(), 1, "postscount should be 1, not %d")
  1771  	ex(co.Has(uid), "saturn should be in the conversation")
  1772  	ex(!co.Has(9999), "uid 9999 should not be in the conversation")
  1773  	uids, err := co.Uids()
  1774  	expectNilErr(t, err)
  1775  	expectIntToBeX(t, len(uids), 2, "uids length should be 2, not %d")
  1776  	exf(uids[0] == uid, "uids[0] should be %d, not %d", uid, uids[0])
  1777  	exf(uids[1] == 1, "uids[1] should be %d, not %d", 1, uids[1])
  1778  	nf(cid, 1)
  1779  
  1780  	expectNilErr(t, c.Convos.Delete(cid))
  1781  	expectIntToBeX(t, co.PostsCount(), 0, "postscount should be 0, not %d")
  1782  	ex(!co.Has(uid), "saturn should not be in a deleted conversation")
  1783  	uids, err = co.Uids()
  1784  	expectNilErr(t, err)
  1785  	expectIntToBeX(t, len(uids), 0, "uids length should be 0, not %d")
  1786  	nf(cid, 0)
  1787  
  1788  	// TODO: More tests
  1789  
  1790  	// Block tests
  1791  
  1792  	ok, err := c.UserBlocks.IsBlockedBy(1, 1)
  1793  	expectNilErr(t, err)
  1794  	ex(!ok, "there shouldn't be any blocks")
  1795  	ok, err = c.UserBlocks.BulkIsBlockedBy([]int{1}, 1)
  1796  	expectNilErr(t, err)
  1797  	ex(!ok, "there shouldn't be any blocks")
  1798  	bf := func(blocker, offset, perPage, expectLen, blockee int) {
  1799  		l, err := c.UserBlocks.BlockedByOffset(blocker, offset, perPage)
  1800  		expectNilErr(t, err)
  1801  		exf(len(l) == expectLen, "there should be %d users blocked by %d not %d", expectLen, blocker, len(l))
  1802  		if len(l) > 0 {
  1803  			exf(l[0] == blockee, "blocked uid should be %d not %d", blockee, l[0])
  1804  		}
  1805  	}
  1806  	nbf := func(blocker, blockee int) {
  1807  		ok, err := c.UserBlocks.IsBlockedBy(1, 2)
  1808  		expectNilErr(t, err)
  1809  		ex(!ok, "there shouldn't be any blocks")
  1810  		ok, err = c.UserBlocks.BulkIsBlockedBy([]int{1}, 2)
  1811  		expectNilErr(t, err)
  1812  		ex(!ok, "there shouldn't be any blocks")
  1813  		expectIntToBeX(t, c.UserBlocks.BlockedByCount(1), 0, "blockedbycount for 1 should be 1, not %d")
  1814  		bf(1, 0, 1, 0, 0)
  1815  		bf(1, 0, 15, 0, 0)
  1816  		bf(1, 1, 15, 0, 0)
  1817  		bf(1, 5, 15, 0, 0)
  1818  	}
  1819  	nbf(1, 2)
  1820  
  1821  	expectNilErr(t, c.UserBlocks.Add(1, 2))
  1822  	ok, err = c.UserBlocks.IsBlockedBy(1, 2)
  1823  	expectNilErr(t, err)
  1824  	ex(ok, "2 should be blocked by 1")
  1825  	expectIntToBeX(t, c.UserBlocks.BlockedByCount(1), 1, "blockedbycount for 1 should be 1, not %d")
  1826  	bf(1, 0, 1, 1, 2)
  1827  	bf(1, 0, 15, 1, 2)
  1828  	bf(1, 1, 15, 0, 0)
  1829  	bf(1, 5, 15, 0, 0)
  1830  
  1831  	// Double add test
  1832  	expectNilErr(t, c.UserBlocks.Add(1, 2))
  1833  	ok, err = c.UserBlocks.IsBlockedBy(1, 2)
  1834  	expectNilErr(t, err)
  1835  	ex(ok, "2 should be blocked by 1")
  1836  	//expectIntToBeX(t, c.UserBlocks.BlockedByCount(1), 1, "blockedbycount for 1 should be 1, not %d") // todo: fix this
  1837  	//bf(1, 0, 1, 1, 2) // todo: fix this
  1838  	//bf(1, 0, 15, 1, 2) // todo: fix this
  1839  	//bf(1, 1, 15, 0, 0) // todo: fix this
  1840  	bf(1, 5, 15, 0, 0)
  1841  
  1842  	expectNilErr(t, c.UserBlocks.Remove(1, 2))
  1843  	nbf(1, 2)
  1844  	// Double remove test
  1845  	expectNilErr(t, c.UserBlocks.Remove(1, 2))
  1846  	nbf(1, 2)
  1847  
  1848  	// TODO: Self-block test
  1849  
  1850  	// TODO: More Block tests
  1851  }
  1852  
  1853  func TestActivityStream(t *testing.T) {
  1854  	miscinit(t)
  1855  	ex, exf := exp(t), expf(t)
  1856  
  1857  	ex(c.Activity.Count() == 0, "activity stream count should be 0")
  1858  	gNone := func(id int) {
  1859  		_, e := c.Activity.Get(id)
  1860  		recordMustNotExist(t, e, "activity item "+strconv.Itoa(id)+" shouldn't exist")
  1861  	}
  1862  	gNone(-1)
  1863  	gNone(0)
  1864  	gNone(1)
  1865  	countAsid := func(asid, count int) {
  1866  		exf(c.ActivityMatches.CountAsid(asid) == count, "activity stream matches count for asid %d should be %d not %d", asid, count, c.ActivityMatches.CountAsid(asid))
  1867  	}
  1868  	countAsid(-1, 0)
  1869  	countAsid(0, 0)
  1870  	countAsid(1, 0)
  1871  
  1872  	a := c.Alert{ActorID: 1, TargetUserID: 1, Event: "like", ElementType: "topic", ElementID: 1}
  1873  	id, e := c.Activity.Add(a)
  1874  	expectNilErr(t, e)
  1875  	ex(id == 1, "new activity item id should be 1")
  1876  
  1877  	ex(c.Activity.Count() == 1, "activity stream count should be 1")
  1878  	al, e := c.Activity.Get(1)
  1879  	expectNilErr(t, e)
  1880  	exf(al.ASID == id, "alert asid should be %d not %d", id, al.ASID)
  1881  	ex(al.ActorID == 1, "alert actorid should be 1")
  1882  	ex(al.TargetUserID == 1, "alert targetuserid should be 1")
  1883  	ex(al.Event == "like", "alert event type should be like")
  1884  	ex(al.ElementType == "topic", "alert element type should be topic")
  1885  	ex(al.ElementID == 1, "alert element id should be 1")
  1886  
  1887  	countAsid(id, 0)
  1888  
  1889  	tuid, e := c.Users.Create("Activity Target", "Activity Target", "", 1, true)
  1890  	expectNilErr(t, e)
  1891  	expectNilErr(t, c.ActivityMatches.Add(tuid, 1))
  1892  	countAsid(id, 1)
  1893  	expectNilErr(t, c.ActivityMatches.Delete(tuid, id))
  1894  	countAsid(id, 0)
  1895  
  1896  	expectNilErr(t, c.ActivityMatches.Add(tuid, 1))
  1897  	countAsid(id, 1)
  1898  	changed, e := c.ActivityMatches.DeleteAndCountChanged(tuid, id)
  1899  	expectNilErr(t, e)
  1900  	exf(changed == 1, "changed should be %d not %d", 1, changed)
  1901  	countAsid(id, 0)
  1902  
  1903  	expectNilErr(t, c.ActivityMatches.Add(tuid, 1))
  1904  	countAsid(id, 1)
  1905  
  1906  	// TODO: Add more tests
  1907  
  1908  	expectNilErr(t, c.Activity.Delete(id))
  1909  	ex(c.Activity.Count() == 0, "activity stream count should be 0")
  1910  	gNone(id)
  1911  	countAsid(id, 0)
  1912  
  1913  	// TODO: More tests
  1914  }
  1915  
  1916  func TestLogs(t *testing.T) {
  1917  	ex, exf := exp(t), expf(t)
  1918  	miscinit(t)
  1919  	gTests := func(s c.LogStore, phrase string) {
  1920  		ex(s.Count() == 0, "There shouldn't be any "+phrase)
  1921  		logs, err := s.GetOffset(0, 25)
  1922  		expectNilErr(t, err)
  1923  		ex(len(logs) == 0, "The log slice should be empty")
  1924  	}
  1925  	gTests(c.ModLogs, "modlogs")
  1926  	gTests(c.AdminLogs, "adminlogs")
  1927  
  1928  	gTests2 := func(s c.LogStore, phrase string) {
  1929  		err := s.Create("something", 0, "bumblefly", "::1", 1)
  1930  		expectNilErr(t, err)
  1931  		count := s.Count()
  1932  		exf(count == 1, "store.Count should return one, not %d", count)
  1933  		logs, err := s.GetOffset(0, 25)
  1934  		recordMustExist(t, err, "We should have at-least one "+phrase)
  1935  		ex(len(logs) == 1, "The length of the log slice should be one")
  1936  
  1937  		l := logs[0]
  1938  		ex(l.Action == "something", "l.Action is not something")
  1939  		ex(l.ElementID == 0, "l.ElementID is not 0")
  1940  		ex(l.ElementType == "bumblefly", "l.ElementType is not bumblefly")
  1941  		ex(l.IP == "::1", "l.IP is not ::1")
  1942  		ex(l.ActorID == 1, "l.ActorID is not 1")
  1943  		// TODO: Add a test for log.DoneAt? Maybe throw in some dates and times which are clearly impossible but which may occur due to timezone bugs?
  1944  	}
  1945  	gTests2(c.ModLogs, "modlog")
  1946  	gTests2(c.AdminLogs, "adminlog")
  1947  }
  1948  
  1949  func TestRegLogs(t *testing.T) {
  1950  	miscinit(t)
  1951  	if !c.PluginsInited {
  1952  		c.InitPlugins()
  1953  	}
  1954  	exf := expf(t)
  1955  
  1956  	mustNone := func() {
  1957  		exf(c.RegLogs.Count() == 0, "count should be %d not %d", 0, c.RegLogs.Count())
  1958  		items, e := c.RegLogs.GetOffset(0, 10)
  1959  		expectNilErr(t, e)
  1960  		exf(len(items) == 0, "len(items) should be %d not %d", 0, len(items))
  1961  		expectNilErr(t, c.RegLogs.Purge())
  1962  		exf(c.RegLogs.Count() == 0, "count should be %d not %d", 0, c.RegLogs.Count())
  1963  		items, e = c.RegLogs.GetOffset(0, 10)
  1964  		expectNilErr(t, e)
  1965  		exf(len(items) == 0, "len(items) should be %d not %d", 0, len(items))
  1966  	}
  1967  	mustNone()
  1968  
  1969  	regLog := &c.RegLogItem{Username: "Aa", Email: "aa@example.com", FailureReason: "fake", Success: false, IP: ""}
  1970  	id, e := regLog.Create()
  1971  	exf(id == 1, "id should be %d not %d", 1, id)
  1972  	expectNilErr(t, e)
  1973  
  1974  	exf(c.RegLogs.Count() == 1, "count should be %d not %d", 1, c.RegLogs.Count())
  1975  	items, e := c.RegLogs.GetOffset(0, 10)
  1976  	expectNilErr(t, e)
  1977  	exf(len(items) == 1, "len(items) should be %d not %d", 1, len(items))
  1978  	// TODO: Add more tests
  1979  
  1980  	expectNilErr(t, c.RegLogs.DeleteOlderThanDays(2))
  1981  
  1982  	exf(c.RegLogs.Count() == 1, "count should be %d not %d", 1, c.RegLogs.Count())
  1983  	items, e = c.RegLogs.GetOffset(0, 10)
  1984  	expectNilErr(t, e)
  1985  	exf(len(items) == 1, "len(items) should be %d not %d", 1, len(items))
  1986  	// TODO: Add more tests
  1987  
  1988  	// TODO: Commit() test?
  1989  	dayAgo := time.Now().AddDate(0, 0, -5)
  1990  	items[0].DoneAt = dayAgo.Format("2006-01-02 15:04:05")
  1991  	expectNilErr(t, items[0].Commit())
  1992  
  1993  	exf(c.RegLogs.Count() == 1, "count should be %d not %d", 1, c.RegLogs.Count())
  1994  	items, e = c.RegLogs.GetOffset(0, 10)
  1995  	expectNilErr(t, e)
  1996  	exf(len(items) == 1, "len(items) should be %d not %d", 1, len(items))
  1997  	// TODO: Add more tests
  1998  
  1999  	expectNilErr(t, c.RegLogs.DeleteOlderThanDays(2))
  2000  	mustNone()
  2001  
  2002  	regLog = &c.RegLogItem{Username: "Aa", Email: "aa@example.com", FailureReason: "fake", Success: false, IP: ""}
  2003  	id, e = regLog.Create()
  2004  	exf(id == 2, "id should be %d not %d", 2, id)
  2005  	expectNilErr(t, e)
  2006  
  2007  	exf(c.RegLogs.Count() == 1, "count should be %d not %d", 1, c.RegLogs.Count())
  2008  	items, e = c.RegLogs.GetOffset(0, 10)
  2009  	expectNilErr(t, e)
  2010  	exf(len(items) == 1, "len(items) should be %d not %d", 1, len(items))
  2011  	// TODO: Add more tests
  2012  
  2013  	expectNilErr(t, c.RegLogs.Purge())
  2014  	mustNone()
  2015  
  2016  	// TODO: Add more tests
  2017  }
  2018  
  2019  func TestLoginLogs(t *testing.T) {
  2020  	miscinit(t)
  2021  	if !c.PluginsInited {
  2022  		c.InitPlugins()
  2023  	}
  2024  	ex, exf := exp(t), expf(t)
  2025  	uid, e := c.Users.Create("Log Test", "Log Test", "", 1, true)
  2026  	expectNilErr(t, e)
  2027  
  2028  	exf(c.LoginLogs.CountUser(-1) == 0, "countuser(-1) should be %d not %d", 0, c.LoginLogs.CountUser(-1))
  2029  	exf(c.LoginLogs.CountUser(0) == 0, "countuser(0) should be %d not %d", 0, c.LoginLogs.CountUser(0))
  2030  	exf(c.LoginLogs.CountUser(1) == 0, "countuser(1) should be %d not %d", 0, c.LoginLogs.CountUser(1))
  2031  	goNone := func(uid, offset, perPage int) {
  2032  		items, e := c.LoginLogs.GetOffset(uid, offset, perPage)
  2033  		expectNilErr(t, e)
  2034  		exf(len(items) == 0, "len(items) should be %d not %d", 0, len(items))
  2035  	}
  2036  	goNone(-1, 0, 10)
  2037  	goNone(0, 0, 10)
  2038  	goNone(1, 0, 10)
  2039  	goNone(1, 1, 10)
  2040  	goNone(1, 0, 0)
  2041  
  2042  	mustNone := func() {
  2043  		exf(c.LoginLogs.Count() == 0, "count should be %d not %d", 0, c.LoginLogs.Count())
  2044  		exf(c.LoginLogs.CountUser(uid) == 0, "countuser(%d) should be %d not %d", uid, 0, c.LoginLogs.CountUser(uid))
  2045  		goNone(uid, 0, 10)
  2046  		goNone(uid, 1, 10)
  2047  		goNone(uid, 0, 0)
  2048  	}
  2049  	mustNone()
  2050  
  2051  	logItem := &c.LoginLogItem{UID: uid, Success: true, IP: ""}
  2052  	_, e = logItem.Create()
  2053  	expectNilErr(t, e)
  2054  
  2055  	exf(c.LoginLogs.Count() == 1, "count should be %d not %d", 1, c.LoginLogs.Count())
  2056  	exf(c.LoginLogs.CountUser(uid) == 1, "countuser(%d) should be %d not %d", uid, 1, c.LoginLogs.CountUser(uid))
  2057  	items, e := c.LoginLogs.GetOffset(uid, 0, 10)
  2058  	expectNilErr(t, e)
  2059  	exf(len(items) == 1, "len(items) should be %d not %d", 1, len(items))
  2060  	// TODO: More tests
  2061  	exf(items[0].UID == uid, "UID should be %d not %d", uid, items[0].UID)
  2062  	ex(items[0].Success, "Success should be true")
  2063  	ex(items[0].IP == "", "IP should be blank")
  2064  	goNone(uid, 1, 10)
  2065  	goNone(uid, 0, 0)
  2066  
  2067  	dayAgo := time.Now().AddDate(0, 0, -5)
  2068  	items[0].DoneAt = dayAgo.Format("2006-01-02 15:04:05")
  2069  	prevDoneAt := items[0].DoneAt
  2070  	expectNilErr(t, items[0].Commit())
  2071  
  2072  	items, e = c.LoginLogs.GetOffset(uid, 0, 10)
  2073  	expectNilErr(t, e)
  2074  	exf(len(items) == 1, "len(items) should be %d not %d", 1, len(items))
  2075  	// TODO: More tests
  2076  	exf(items[0].UID == uid, "UID should be %d not %d", uid, items[0].UID)
  2077  	ex(items[0].Success, "Success should be true")
  2078  	ex(items[0].IP == "", "IP should be blank")
  2079  	exf(items[0].DoneAt == prevDoneAt, "DoneAt should be %s not %s", prevDoneAt, items[0].DoneAt)
  2080  	goNone(uid, 1, 10)
  2081  	goNone(uid, 0, 0)
  2082  
  2083  	expectNilErr(t, c.LoginLogs.DeleteOlderThanDays(2))
  2084  	mustNone()
  2085  
  2086  	logItem = &c.LoginLogItem{UID: uid, Success: true, IP: ""}
  2087  	_, e = logItem.Create()
  2088  	expectNilErr(t, e)
  2089  
  2090  	exf(c.LoginLogs.Count() == 1, "count should be %d not %d", 1, c.LoginLogs.Count())
  2091  	exf(c.LoginLogs.CountUser(uid) == 1, "countuser(%d) should be %d not %d", uid, 1, c.LoginLogs.CountUser(uid))
  2092  	items, e = c.LoginLogs.GetOffset(uid, 0, 10)
  2093  	expectNilErr(t, e)
  2094  	exf(len(items) == 1, "len(items) should be %d not %d", 1, len(items))
  2095  	// TODO: More tests
  2096  	exf(items[0].UID == uid, "UID should be %d not %d", uid, items[0].UID)
  2097  	ex(items[0].Success, "Success should be true")
  2098  	ex(items[0].IP == "", "IP should be blank")
  2099  	goNone(uid, 1, 10)
  2100  	goNone(uid, 0, 0)
  2101  
  2102  	expectNilErr(t, c.LoginLogs.Purge())
  2103  	mustNone()
  2104  }
  2105  
  2106  func TestPluginManager(t *testing.T) {
  2107  	miscinit(t)
  2108  	if !c.PluginsInited {
  2109  		c.InitPlugins()
  2110  	}
  2111  	ex := exp(t)
  2112  
  2113  	_, ok := c.Plugins["fairy-dust"]
  2114  	ex(!ok, "Plugin fairy-dust shouldn't exist")
  2115  	pl, ok := c.Plugins["bbcode"]
  2116  	ex(ok, "Plugin bbcode should exist")
  2117  	ex(!pl.Installable, "Plugin bbcode shouldn't be installable")
  2118  	ex(!pl.Installed, "Plugin bbcode shouldn't be 'installed'")
  2119  	ex(!pl.Active, "Plugin bbcode shouldn't be active")
  2120  	active, e := pl.BypassActive()
  2121  	expectNilErr(t, e)
  2122  	ex(!active, "Plugin bbcode shouldn't be active in the database either")
  2123  	hasPlugin, e := pl.InDatabase()
  2124  	expectNilErr(t, e)
  2125  	ex(!hasPlugin, "Plugin bbcode shouldn't exist in the database")
  2126  	// TODO: Add some test cases for SetActive and SetInstalled before calling AddToDatabase
  2127  
  2128  	expectNilErr(t, pl.AddToDatabase(true, false))
  2129  	ex(!pl.Installable, "Plugin bbcode shouldn't be installable")
  2130  	ex(!pl.Installed, "Plugin bbcode shouldn't be 'installed'")
  2131  	ex(pl.Active, "Plugin bbcode should be active")
  2132  	active, e = pl.BypassActive()
  2133  	expectNilErr(t, e)
  2134  	ex(active, "Plugin bbcode should be active in the database too")
  2135  	hasPlugin, e = pl.InDatabase()
  2136  	expectNilErr(t, e)
  2137  	ex(hasPlugin, "Plugin bbcode should exist in the database")
  2138  	ex(pl.Init != nil, "Plugin bbcode should have an init function")
  2139  	expectNilErr(t, pl.Init(pl))
  2140  
  2141  	expectNilErr(t, pl.SetActive(true))
  2142  	ex(!pl.Installable, "Plugin bbcode shouldn't be installable")
  2143  	ex(!pl.Installed, "Plugin bbcode shouldn't be 'installed'")
  2144  	ex(pl.Active, "Plugin bbcode should still be active")
  2145  	active, e = pl.BypassActive()
  2146  	expectNilErr(t, e)
  2147  	ex(active, "Plugin bbcode should still be active in the database too")
  2148  	hasPlugin, e = pl.InDatabase()
  2149  	expectNilErr(t, e)
  2150  	ex(hasPlugin, "Plugin bbcode should still exist in the database")
  2151  
  2152  	expectNilErr(t, pl.SetActive(false))
  2153  	ex(!pl.Installable, "Plugin bbcode shouldn't be installable")
  2154  	ex(!pl.Installed, "Plugin bbcode shouldn't be 'installed'")
  2155  	ex(!pl.Active, "Plugin bbcode shouldn't be active")
  2156  	active, e = pl.BypassActive()
  2157  	expectNilErr(t, e)
  2158  	ex(!active, "Plugin bbcode shouldn't be active in the database")
  2159  	hasPlugin, e = pl.InDatabase()
  2160  	expectNilErr(t, e)
  2161  	ex(hasPlugin, "Plugin bbcode should still exist in the database")
  2162  	ex(pl.Deactivate != nil, "Plugin bbcode should have an init function")
  2163  	pl.Deactivate(pl) // Returns nothing
  2164  
  2165  	// Not installable, should not be mutated
  2166  	ex(pl.SetInstalled(true) == c.ErrPluginNotInstallable, "Plugin was set as installed despite not being installable")
  2167  	ex(!pl.Installable, "Plugin bbcode shouldn't be installable")
  2168  	ex(!pl.Installed, "Plugin bbcode shouldn't be 'installed'")
  2169  	ex(!pl.Active, "Plugin bbcode shouldn't be active")
  2170  	active, e = pl.BypassActive()
  2171  	expectNilErr(t, e)
  2172  	ex(!active, "Plugin bbcode shouldn't be active in the database either")
  2173  	hasPlugin, e = pl.InDatabase()
  2174  	expectNilErr(t, e)
  2175  	ex(hasPlugin, "Plugin bbcode should still exist in the database")
  2176  
  2177  	ex(pl.SetInstalled(false) == c.ErrPluginNotInstallable, "Plugin was set as not installed despite not being installable")
  2178  	ex(!pl.Installable, "Plugin bbcode shouldn't be installable")
  2179  	ex(!pl.Installed, "Plugin bbcode shouldn't be 'installed'")
  2180  	ex(!pl.Active, "Plugin bbcode shouldn't be active")
  2181  	active, e = pl.BypassActive()
  2182  	expectNilErr(t, e)
  2183  	ex(!active, "Plugin bbcode shouldn't be active in the database either")
  2184  	hasPlugin, e = pl.InDatabase()
  2185  	expectNilErr(t, e)
  2186  	ex(hasPlugin, "Plugin bbcode should still exist in the database")
  2187  
  2188  	// This isn't really installable, but we want to get a few tests done before getting plugins which are stateful
  2189  	pl.Installable = true
  2190  	expectNilErr(t, pl.SetInstalled(true))
  2191  	ex(pl.Installable, "Plugin bbcode should be installable")
  2192  	ex(pl.Installed, "Plugin bbcode should be 'installed'")
  2193  	ex(!pl.Active, "Plugin bbcode shouldn't be active")
  2194  	active, e = pl.BypassActive()
  2195  	expectNilErr(t, e)
  2196  	ex(!active, "Plugin bbcode shouldn't be active in the database either")
  2197  	hasPlugin, e = pl.InDatabase()
  2198  	expectNilErr(t, e)
  2199  	ex(hasPlugin, "Plugin bbcode should still exist in the database")
  2200  
  2201  	expectNilErr(t, pl.SetInstalled(false))
  2202  	ex(pl.Installable, "Plugin bbcode should be installable")
  2203  	ex(!pl.Installed, "Plugin bbcode shouldn't be 'installed'")
  2204  	ex(!pl.Active, "Plugin bbcode shouldn't be active")
  2205  	active, e = pl.BypassActive()
  2206  	expectNilErr(t, e)
  2207  	ex(!active, "Plugin bbcode shouldn't be active in the database either")
  2208  	hasPlugin, e = pl.InDatabase()
  2209  	expectNilErr(t, e)
  2210  	ex(hasPlugin, "Plugin bbcode should still exist in the database")
  2211  
  2212  	// Bugs sometimes arise when we try to delete a hook when there are multiple, so test for that
  2213  	// TODO: Do a finer grained test for that case...? A bigger test might catch more odd cases with multiple plugins
  2214  	pl2, ok := c.Plugins["markdown"]
  2215  	ex(ok, "Plugin markdown should exist")
  2216  	ex(!pl2.Installable, "Plugin markdown shouldn't be installable")
  2217  	ex(!pl2.Installed, "Plugin markdown shouldn't be 'installed'")
  2218  	ex(!pl2.Active, "Plugin markdown shouldn't be active")
  2219  	active, e = pl2.BypassActive()
  2220  	expectNilErr(t, e)
  2221  	ex(!active, "Plugin markdown shouldn't be active in the database either")
  2222  	hasPlugin, e = pl2.InDatabase()
  2223  	expectNilErr(t, e)
  2224  	ex(!hasPlugin, "Plugin markdown shouldn't exist in the database")
  2225  
  2226  	expectNilErr(t, pl2.AddToDatabase(true, false))
  2227  	expectNilErr(t, pl2.Init(pl2))
  2228  	expectNilErr(t, pl.SetActive(true))
  2229  	expectNilErr(t, pl.Init(pl))
  2230  	pl2.Deactivate(pl2)
  2231  	expectNilErr(t, pl2.SetActive(false))
  2232  	pl.Deactivate(pl)
  2233  	expectNilErr(t, pl.SetActive(false))
  2234  
  2235  	// Hook tests
  2236  	ht := func() *c.HookTable {
  2237  		return c.GetHookTable()
  2238  	}
  2239  	ex(ht().Sshook("haha", "ho") == "ho", "Sshook shouldn't have anything bound to it yet")
  2240  	handle := func(in string) (out string) {
  2241  		return in + "hi"
  2242  	}
  2243  	pl.AddHook("haha", handle)
  2244  	ex(ht().Sshook("haha", "ho") == "hohi", "Sshook didn't give hohi")
  2245  	pl.RemoveHook("haha", handle)
  2246  	ex(ht().Sshook("haha", "ho") == "ho", "Sshook shouldn't have anything bound to it anymore")
  2247  
  2248  	/*ex(ht().Hook("haha", "ho") == "ho", "Hook shouldn't have anything bound to it yet")
  2249  	handle2 := func(inI interface{}) (out interface{}) {
  2250  		return inI.(string) + "hi"
  2251  	}
  2252  	pl.AddHook("hehe", handle2)
  2253  	ex(ht().Hook("hehe", "ho").(string) == "hohi", "Hook didn't give hohi")
  2254  	pl.RemoveHook("hehe", handle2)
  2255  	ex(ht().Hook("hehe", "ho").(string) == "ho", "Hook shouldn't have anything bound to it anymore")*/
  2256  
  2257  	// TODO: Add tests for more hook types
  2258  }
  2259  
  2260  func TestPhrases(t *testing.T) {
  2261  	getPhrase := phrases.GetPermPhrase
  2262  	tp := func(name, expects string) {
  2263  		res := getPhrase(name)
  2264  		expect(t, res == expects, "Not the expected phrase, got '"+res+"' instead")
  2265  	}
  2266  	tp("BanUsers", "Can ban users")
  2267  	tp("NoSuchPerm", "{lang.perms[NoSuchPerm]}")
  2268  	tp("ViewTopic", "Can view topics")
  2269  	tp("NoSuchPerm", "{lang.perms[NoSuchPerm]}")
  2270  
  2271  	// TODO: Cover the other phrase types, also try switching between languages to see if anything strange happens
  2272  }
  2273  
  2274  func TestMetaStore(t *testing.T) {
  2275  	ex, exf := exp(t), expf(t)
  2276  	m, e := c.Meta.Get("magic")
  2277  	ex(m == "", "meta var magic should be empty")
  2278  	recordMustNotExist(t, e, "meta var magic should not exist")
  2279  
  2280  	expectVal := func(name, expect string) {
  2281  		m, e = c.Meta.Get(name)
  2282  		expectNilErr(t, e)
  2283  		exf(m == expect, "meta var %s should be %s", name, expect)
  2284  	}
  2285  
  2286  	expectNilErr(t, c.Meta.Set("magic", "lol"))
  2287  	expectVal("magic", "lol")
  2288  	expectNilErr(t, c.Meta.Set("magic", "wha"))
  2289  	expectVal("magic", "wha")
  2290  
  2291  	m, e = c.Meta.Get("giggle")
  2292  	ex(m == "", "meta var giggle should be empty")
  2293  	recordMustNotExist(t, e, "meta var giggle should not exist")
  2294  
  2295  	expectNilErr(t, c.Meta.SetInt("magic", 1))
  2296  	expectVal("magic", "1")
  2297  	expectNilErr(t, c.Meta.SetInt64("magic", 5))
  2298  	expectVal("magic", "5")
  2299  }
  2300  
  2301  func TestPages(t *testing.T) {
  2302  	ex := exp(t)
  2303  	ex(c.Pages.Count() == 0, "Page count should be 0")
  2304  	_, e := c.Pages.Get(1)
  2305  	recordMustNotExist(t, e, "Page 1 should not exist yet")
  2306  	expectNilErr(t, c.Pages.Delete(-1))
  2307  	expectNilErr(t, c.Pages.Delete(0))
  2308  	expectNilErr(t, c.Pages.Delete(1))
  2309  	_, e = c.Pages.Get(1)
  2310  	recordMustNotExist(t, e, "Page 1 should not exist yet")
  2311  	//e = c.Pages.Reload(1)
  2312  	//recordMustNotExist(t,e,"Page 1 should not exist yet")
  2313  
  2314  	ipage := c.BlankCustomPage()
  2315  	ipage.Name = "test"
  2316  	ipage.Title = "Test"
  2317  	ipage.Body = "A test page"
  2318  	pid, e := ipage.Create()
  2319  	expectNilErr(t, e)
  2320  	ex(pid == 1, "The first page should have an ID of 1")
  2321  	ex(c.Pages.Count() == 1, "Page count should be 1")
  2322  
  2323  	test := func(pid int, ep *c.CustomPage) {
  2324  		p, e := c.Pages.Get(pid)
  2325  		expectNilErr(t, e)
  2326  		ex(p.Name == ep.Name, "The page name should be "+ep.Name)
  2327  		ex(p.Title == ep.Title, "The page title should be "+ep.Title)
  2328  		ex(p.Body == ep.Body, "The page body should be "+ep.Body)
  2329  	}
  2330  	test(1, ipage)
  2331  
  2332  	opage, err := c.Pages.Get(1)
  2333  	expectNilErr(t, err)
  2334  	opage.Name = "t"
  2335  	opage.Title = "T"
  2336  	opage.Body = "testing"
  2337  	expectNilErr(t, opage.Commit())
  2338  
  2339  	test(1, opage)
  2340  
  2341  	expectNilErr(t, c.Pages.Delete(1))
  2342  	ex(c.Pages.Count() == 0, "Page count should be 0")
  2343  	_, e = c.Pages.Get(1)
  2344  	recordMustNotExist(t, e, "Page 1 should not exist")
  2345  	//e = c.Pages.Reload(1)
  2346  	//recordMustNotExist(t,e,"Page 1 should not exist")
  2347  
  2348  	// TODO: More tests
  2349  }
  2350  
  2351  func TestWordFilters(t *testing.T) {
  2352  	ex, exf := exp(t), expf(t)
  2353  	// TODO: Test the word filters and their store
  2354  	ex(c.WordFilters.Length() == 0, "Word filter list should be empty")
  2355  	ex(c.WordFilters.EstCount() == 0, "Word filter list should be empty")
  2356  	ex(c.WordFilters.Count() == 0, "Word filter list should be empty")
  2357  	filters, err := c.WordFilters.GetAll()
  2358  	expectNilErr(t, err) // TODO: Slightly confusing that we don't get ErrNoRow here
  2359  	ex(len(filters) == 0, "Word filter map should be empty")
  2360  	// TODO: Add a test for ParseMessage relating to word filters
  2361  	_, err = c.WordFilters.Get(1)
  2362  	recordMustNotExist(t, err, "filter 1 should not exist")
  2363  
  2364  	wfid, err := c.WordFilters.Create("imbecile", "lovely")
  2365  	expectNilErr(t, err)
  2366  	ex(wfid == 1, "The first word filter should have an ID of 1")
  2367  	ex(c.WordFilters.Length() == 1, "Word filter list should not be empty")
  2368  	ex(c.WordFilters.EstCount() == 1, "Word filter list should not be empty")
  2369  	ex(c.WordFilters.Count() == 1, "Word filter list should not be empty")
  2370  
  2371  	ftest := func(f *c.WordFilter, id int, find, replace string) {
  2372  		exf(f.ID == id, "Word filter ID should be %d, not %d", id, f.ID)
  2373  		exf(f.Find == find, "Word filter needle should be '%s', not '%s'", find, f.Find)
  2374  		exf(f.Replace == replace, "Word filter replacement should be '%s', not '%s'", replace, f.Replace)
  2375  	}
  2376  
  2377  	filters, err = c.WordFilters.GetAll()
  2378  	expectNilErr(t, err)
  2379  	ex(len(filters) == 1, "Word filter map should not be empty")
  2380  	ftest(filters[1], 1, "imbecile", "lovely")
  2381  
  2382  	filter, err := c.WordFilters.Get(1)
  2383  	expectNilErr(t, err)
  2384  	ftest(filter, 1, "imbecile", "lovely")
  2385  
  2386  	// Update
  2387  	expectNilErr(t, c.WordFilters.Update(1, "b", "a"))
  2388  
  2389  	ex(c.WordFilters.Length() == 1, "Word filter list should not be empty")
  2390  	ex(c.WordFilters.EstCount() == 1, "Word filter list should not be empty")
  2391  	ex(c.WordFilters.Count() == 1, "Word filter list should not be empty")
  2392  
  2393  	filters, err = c.WordFilters.GetAll()
  2394  	expectNilErr(t, err)
  2395  	ex(len(filters) == 1, "Word filter map should not be empty")
  2396  	ftest(filters[1], 1, "b", "a")
  2397  
  2398  	filter, err = c.WordFilters.Get(1)
  2399  	expectNilErr(t, err)
  2400  	ftest(filter, 1, "b", "a")
  2401  
  2402  	// TODO: Add a test for ParseMessage relating to word filters
  2403  
  2404  	expectNilErr(t, c.WordFilters.Delete(1))
  2405  
  2406  	ex(c.WordFilters.Length() == 0, "Word filter list should be empty")
  2407  	ex(c.WordFilters.EstCount() == 0, "Word filter list should be empty")
  2408  	ex(c.WordFilters.Count() == 0, "Word filter list should be empty")
  2409  	filters, err = c.WordFilters.GetAll()
  2410  	expectNilErr(t, err) // TODO: Slightly confusing that we don't get ErrNoRow here
  2411  	ex(len(filters) == 0, "Word filter map should be empty")
  2412  	_, err = c.WordFilters.Get(1)
  2413  	recordMustNotExist(t, err, "filter 1 should not exist")
  2414  
  2415  	// TODO: Any more tests we could do?
  2416  }
  2417  
  2418  func TestMFAStore(t *testing.T) {
  2419  	exf := expf(t)
  2420  
  2421  	mustNone := func() {
  2422  		_, e := c.MFAstore.Get(-1)
  2423  		recordMustNotExist(t, e, "mfa uid -1 should not exist")
  2424  		_, e = c.MFAstore.Get(0)
  2425  		recordMustNotExist(t, e, "mfa uid 0 should not exist")
  2426  		_, e = c.MFAstore.Get(1)
  2427  		recordMustNotExist(t, e, "mfa uid 1 should not exist")
  2428  	}
  2429  	mustNone()
  2430  
  2431  	secret, e := c.GenerateGAuthSecret()
  2432  	expectNilErr(t, e)
  2433  	expectNilErr(t, c.MFAstore.Create(secret, 1))
  2434  	_, e = c.MFAstore.Get(0)
  2435  	recordMustNotExist(t, e, "mfa uid 0 should not exist")
  2436  	var scratches []string
  2437  	it, e := c.MFAstore.Get(1)
  2438  	test := func(j int) {
  2439  		expectNilErr(t, e)
  2440  		exf(it.UID == 1, "UID should be 1 not %d", it.UID)
  2441  		exf(it.Secret == secret, "Secret should be '%s' not %s", secret, it.Secret)
  2442  		exf(len(it.Scratch) == 8, "Scratch should be 8 not %d", len(it.Scratch))
  2443  		for i, scratch := range it.Scratch {
  2444  			exf(scratch != "", "scratch %d should not be empty", i)
  2445  			if scratches != nil {
  2446  				if j == i {
  2447  					exf(scratches[i] != scratch, "scratches[%d] should not be %s", i, scratches[i])
  2448  				} else {
  2449  					exf(scratches[i] == scratch, "scratches[%d] should be %s not %s", i, scratches[i], scratch)
  2450  				}
  2451  			}
  2452  		}
  2453  		scratches = make([]string, 8)
  2454  		copy(scratches, it.Scratch)
  2455  	}
  2456  	test(0)
  2457  	for i := 0; i < len(scratches); i++ {
  2458  		expectNilErr(t, it.BurnScratch(i))
  2459  		it, e = c.MFAstore.Get(1)
  2460  		test(i)
  2461  	}
  2462  	token, e := gauth.GetTOTPToken(secret)
  2463  	expectNilErr(t, e)
  2464  	expectNilErr(t, c.Auth.ValidateMFAToken(token, 1))
  2465  	expectNilErr(t, it.Delete())
  2466  	mustNone()
  2467  }
  2468  
  2469  // TODO: Expand upon the valid characters which can go in URLs?
  2470  func TestSlugs(t *testing.T) {
  2471  	l := &MEPairList{nil}
  2472  	c.Config.BuildSlugs = true // Flip this switch, otherwise all the tests will fail
  2473  
  2474  	l.Add("Unknown", "unknown")
  2475  	l.Add("Unknown2", "unknown2")
  2476  	l.Add("Unknown ", "unknown")
  2477  	l.Add("Unknown 2", "unknown-2")
  2478  	l.Add("Unknown  2", "unknown-2")
  2479  	l.Add("Admin Alice", "admin-alice")
  2480  	l.Add("Admin_Alice", "adminalice")
  2481  	l.Add("Admin_Alice-", "adminalice")
  2482  	l.Add("-Admin_Alice-", "adminalice")
  2483  	l.Add("-Admin@Alice-", "adminalice")
  2484  	l.Add("-AdminπŸ˜€Alice-", "adminalice")
  2485  	l.Add("u", "u")
  2486  	l.Add("", "untitled")
  2487  	l.Add(" ", "untitled")
  2488  	l.Add("-", "untitled")
  2489  	l.Add("--", "untitled")
  2490  	l.Add("Γ©", "Γ©")
  2491  	l.Add("-Γ©-", "Γ©")
  2492  	l.Add("-δ½ ε₯½-", "untitled")
  2493  	l.Add("-こにけは-", "untitled")
  2494  
  2495  	for _, item := range l.Items {
  2496  		t.Log("Testing string '" + item.Msg + "'")
  2497  		res := c.NameToSlug(item.Msg)
  2498  		if res != item.Expects {
  2499  			t.Error("Bad output:", "'"+res+"'")
  2500  			t.Error("Expected:", item.Expects)
  2501  		}
  2502  	}
  2503  }
  2504  
  2505  func TestWidgets(t *testing.T) {
  2506  	ex, exf := exp(t), expf(t)
  2507  	_, e := c.Widgets.Get(1)
  2508  	recordMustNotExist(t, e, "There shouldn't be any widgets by default")
  2509  	widgets := c.Docks.RightSidebar.Items
  2510  	exf(len(widgets) == 0, "RightSidebar should have 0 items, not %d", len(widgets))
  2511  
  2512  	widget := &c.Widget{Position: 0, Side: "rightSidebar", Type: "simple", Enabled: true, Location: "global"}
  2513  	ewidget := &c.WidgetEdit{widget, map[string]string{"Name": "Test", "Text": "Testing"}}
  2514  	wid, e := ewidget.Create()
  2515  	expectNilErr(t, e)
  2516  	ex(wid == 1, "wid should be 1")
  2517  
  2518  	wtest := func(w, w2 *c.Widget) {
  2519  		ex(w.Position == w2.Position, "wrong position")
  2520  		ex(w.Side == w2.Side, "wrong side")
  2521  		ex(w.Type == w2.Type, "wrong type")
  2522  		ex(w.Enabled == w2.Enabled, "wrong enabled")
  2523  		ex(w.Location == w2.Location, "wrong location")
  2524  	}
  2525  
  2526  	// TODO: Do a test for the widget body
  2527  	widget2, e := c.Widgets.Get(1)
  2528  	expectNilErr(t, e)
  2529  	wtest(widget, widget2)
  2530  
  2531  	widgets = c.Docks.RightSidebar.Items
  2532  	exf(len(widgets) == 1, "RightSidebar should have 1 item, not %d", len(widgets))
  2533  	wtest(widget, widgets[0])
  2534  
  2535  	widget2.Enabled = false
  2536  	ewidget = &c.WidgetEdit{widget2, map[string]string{"Name": "Test", "Text": "Testing"}}
  2537  	expectNilErr(t, ewidget.Commit())
  2538  
  2539  	widget2, e = c.Widgets.Get(1)
  2540  	expectNilErr(t, e)
  2541  	widget.Enabled = false
  2542  	wtest(widget, widget2)
  2543  
  2544  	widgets = c.Docks.RightSidebar.Items
  2545  	exf(len(widgets) == 1, "RightSidebar should have 1 item, not %d", len(widgets))
  2546  	widget.Enabled = false
  2547  	wtest(widget, widgets[0])
  2548  
  2549  	expectNilErr(t, widget2.Delete())
  2550  
  2551  	_, e = c.Widgets.Get(1)
  2552  	recordMustNotExist(t, e, "There shouldn't be any widgets anymore")
  2553  	widgets = c.Docks.RightSidebar.Items
  2554  	exf(len(widgets) == 0, "RightSidebar should have 0 items, not %d", len(widgets))
  2555  }
  2556  
  2557  /*type ForumActionStoreInt interface {
  2558  	Get(faid int) (*ForumAction, error)
  2559  	GetInForum(fid int) ([]*ForumAction, error)
  2560  	GetAll() ([]*ForumAction, error)
  2561  	GetNewTopicActions(fid int) ([]*ForumAction, error)
  2562  
  2563  	Add(fa *ForumAction) (int, error)
  2564  	Delete(faid int) error
  2565  	Exists(faid int) bool
  2566  	Count() int
  2567  	CountInForum(fid int) int
  2568  
  2569  	DailyTick() error
  2570  }*/
  2571  
  2572  func TestForumActions(t *testing.T) {
  2573  	ex, exf, s := exp(t), expf(t), c.ForumActionStore
  2574  
  2575  	count := s.CountInForum(-1)
  2576  	exf(count == 0, "count should be %d not %d", 0, count)
  2577  	count = s.CountInForum(0)
  2578  	exf(count == 0, "count in 0 should be %d not %d", 0, count)
  2579  	ex(!s.Exists(-1), "faid -1 should not exist")
  2580  	ex(!s.Exists(0), "faid 0 should not exist")
  2581  	_, e := s.Get(-1)
  2582  	recordMustNotExist(t, e, "faid -1 should not exist")
  2583  	_, e = s.Get(0)
  2584  	recordMustNotExist(t, e, "faid 0 should not exist")
  2585  
  2586  	noActions := func(fid, faid int) {
  2587  		/*sfid, */ sfaid := /*strconv.Itoa(fid), */ strconv.Itoa(faid)
  2588  		count := s.Count()
  2589  		exf(count == 0, "count should be %d not %d", 0, count)
  2590  		count = s.CountInForum(fid)
  2591  		exf(count == 0, "count in %d should be %d not %d", fid, 0, count)
  2592  		exf(!s.Exists(faid), "faid %d should not exist", faid)
  2593  		_, e := s.Get(faid)
  2594  		recordMustNotExist(t, e, "faid "+sfaid+" should not exist")
  2595  		//exf(fa == nil, "fa should be nil not %+v", fa)
  2596  		fas, e := s.GetInForum(fid)
  2597  		//recordMustNotExist(t, e, "fid "+sfid+" should not have any actions")
  2598  		expectNilErr(t, e) // TODO: Why does this not return ErrNoRows?
  2599  		exf(len(fas) == 0, "len(fas) should be %d not %d", 0, len(fas))
  2600  		fas, e = s.GetAll()
  2601  		//recordMustNotExist(t, e, "there should not be any actions")
  2602  		expectNilErr(t, e) // TODO: Why does this not return ErrNoRows?
  2603  		exf(len(fas) == 0, "len(fas) should be %d not %d", 0, len(fas))
  2604  		fas, e = s.GetNewTopicActions(fid)
  2605  		//recordMustNotExist(t, e, "fid "+sfid+" should not have any new topic actions")
  2606  		expectNilErr(t, e) // TODO: Why does this not return ErrNoRows?
  2607  		exf(len(fas) == 0, "len(fas) should be %d not %d", 0, len(fas))
  2608  	}
  2609  	noActions(1, 1)
  2610  
  2611  	fid, e := c.Forums.Create("Forum Action Test", "Forum Action Test", true, "")
  2612  	expectNilErr(t, e)
  2613  	noActions(fid, 1)
  2614  
  2615  	faid, e := c.ForumActionStore.Add(&c.ForumAction{
  2616  		Forum:                      fid,
  2617  		RunOnTopicCreation:         false,
  2618  		RunDaysAfterTopicCreation:  1,
  2619  		RunDaysAfterTopicLastReply: 0,
  2620  		Action:                     c.ForumActionLock,
  2621  		Extra:                      "",
  2622  	})
  2623  	expectNilErr(t, e)
  2624  	exf(faid == 1, "faid should be %d not %d", 1, faid)
  2625  	count = s.Count()
  2626  	exf(count == 1, "count should be %d not %d", 1, count)
  2627  	count = s.CountInForum(fid)
  2628  	exf(count == 1, "count in %d should be %d not %d", fid, 1, count)
  2629  	exf(s.Exists(faid), "faid %d should exist", faid)
  2630  
  2631  	fa, e := s.Get(faid)
  2632  	expectNilErr(t, e)
  2633  	exf(fa.ID == faid, "fa.ID should be %d not %d", faid, fa.ID)
  2634  	exf(fa.Forum == fid, "fa.Forum should be %d not %d", fid, fa.Forum)
  2635  	exf(fa.RunOnTopicCreation == false, "fa.RunOnTopicCreation should be false")
  2636  	exf(fa.RunDaysAfterTopicCreation == 1, "fa.RunDaysAfterTopicCreation should be %d not %d", 1, fa.RunDaysAfterTopicCreation)
  2637  	exf(fa.RunDaysAfterTopicLastReply == 0, "fa.RunDaysAfterTopicLastReply should be %d not %d", 0, fa.RunDaysAfterTopicLastReply)
  2638  	exf(fa.Action == c.ForumActionLock, "fa.Action should be %d not %d", c.ForumActionLock, fa.Action)
  2639  	exf(fa.Extra == "", "fa.Extra should be '%s' not '%s'", "", fa.Extra)
  2640  
  2641  	tid, e := c.Topics.Create(fid, "Forum Action Topic", "Forum Action Topic", 1, "")
  2642  	expectNilErr(t, e)
  2643  	topic, e := c.Topics.Get(tid)
  2644  	expectNilErr(t, e)
  2645  	ex(!topic.IsClosed, "topic.IsClosed should be false")
  2646  	dayAgo := time.Now().AddDate(0, 0, -5)
  2647  	expectNilErr(t, topic.TestSetCreatedAt(dayAgo))
  2648  	expectNilErr(t, fa.Run())
  2649  	topic, e = c.Topics.Get(tid)
  2650  	expectNilErr(t, e)
  2651  	ex(topic.IsClosed, "topic.IsClosed should be true")
  2652  	/*_, e = c.Rstore.Create(topic, "Forum Action Reply", "", 1)
  2653  	expectNilErr(t, e)*/
  2654  
  2655  	tid, e = c.Topics.Create(fid, "Forum Action Topic 2", "Forum Action Topic 2", 1, "")
  2656  	expectNilErr(t, e)
  2657  	topic, e = c.Topics.Get(tid)
  2658  	expectNilErr(t, e)
  2659  	ex(!topic.IsClosed, "topic.IsClosed should be false")
  2660  	expectNilErr(t, fa.Run())
  2661  	topic, e = c.Topics.Get(tid)
  2662  	expectNilErr(t, e)
  2663  	ex(!topic.IsClosed, "topic.IsClosed should be false")
  2664  
  2665  	_ = tid
  2666  
  2667  	expectNilErr(t, s.Delete(faid))
  2668  	noActions(fid, faid)
  2669  
  2670  	// TODO: Bulk lock tests
  2671  	faid, e = c.ForumActionStore.Add(&c.ForumAction{
  2672  		Forum:                      fid,
  2673  		RunOnTopicCreation:         false,
  2674  		RunDaysAfterTopicCreation:  2,
  2675  		RunDaysAfterTopicLastReply: 0,
  2676  		Action:                     c.ForumActionLock,
  2677  		Extra:                      "",
  2678  	})
  2679  	expectNilErr(t, e)
  2680  
  2681  	var l []int
  2682  	addTopic := func() {
  2683  		tid, e = c.Topics.Create(fid, "Forum Action Topic 2", "Forum Action Topic 2", 1, "")
  2684  		expectNilErr(t, e)
  2685  		topic, e := c.Topics.Get(tid)
  2686  		expectNilErr(t, e)
  2687  		ex(!topic.IsClosed, "topic.IsClosed should be false")
  2688  		dayAgo := time.Now().AddDate(0, 0, -5)
  2689  		expectNilErr(t, topic.TestSetCreatedAt(dayAgo))
  2690  		l = append(l, tid)
  2691  	}
  2692  	lTest := func() {
  2693  		for _, ll := range l {
  2694  			to, e := c.Topics.Get(ll)
  2695  			expectNilErr(t, e)
  2696  			ex(to.IsClosed, "to.IsClosed should be true")
  2697  		}
  2698  		l = nil
  2699  	}
  2700  
  2701  	addTopic()
  2702  	addTopic()
  2703  	addTopic()
  2704  	addTopic()
  2705  	addTopic()
  2706  	addTopic()
  2707  	addTopic()
  2708  	addTopic()
  2709  	addTopic()
  2710  	addTopic()
  2711  	expectNilErr(t, fa.Run())
  2712  	lTest()
  2713  	// TODO: Create a method on the *ForumAction to get the count of topics which it could be run on and add a test to verify the count is as expected.
  2714  
  2715  	addTopic()
  2716  	addTopic()
  2717  	addTopic()
  2718  	addTopic()
  2719  	addTopic()
  2720  	addTopic()
  2721  	addTopic()
  2722  	addTopic()
  2723  	addTopic()
  2724  	addTopic()
  2725  	addTopic()
  2726  	expectNilErr(t, fa.Run())
  2727  	lTest()
  2728  	// TODO: Create a method on the *ForumAction to get the count of topics which it could be run on and add a test to verify the count is as expected.
  2729  }
  2730  
  2731  func TestTopicList(t *testing.T) {
  2732  	ex, exf := exp(t), expf(t)
  2733  	fid, err := c.Forums.Create("Test Forum", "Desc for test forum", true, "")
  2734  	expectNilErr(t, err)
  2735  	tint := c.TopicList.(c.TopicListIntTest)
  2736  
  2737  	testPagi := func(p c.Paginator, pageList []int, page, lastPage int) {
  2738  		exf(len(p.PageList) == len(pageList), "len(pagi.PageList) should be %d not %d", len(pageList), len(p.PageList))
  2739  		for i, page := range pageList {
  2740  			exf(p.PageList[i] == page, "pagi.PageList[%d] should be %d not %d", i, page, p.PageList[i])
  2741  		}
  2742  		exf(p.Page == page, "pagi.Page should be %d not %d", page, p.Page)
  2743  		exf(p.LastPage == lastPage, "pagi.LastPage should be %d not %d", lastPage, p.LastPage)
  2744  	}
  2745  	test := func(topicList []*c.TopicsRow, pagi c.Paginator, listLen int, pagi2 c.Paginator, tid1 int) {
  2746  		exf(len(topicList) == listLen, "len(topicList) should be %d not %d", listLen, len(topicList))
  2747  		if len(topicList) > 0 {
  2748  			topic := topicList[0]
  2749  			exf(topic.ID == tid1, "topic.ID should be %d not %d", tid1, topic.ID)
  2750  		}
  2751  		testPagi(pagi, pagi2.PageList, pagi2.Page, pagi2.LastPage)
  2752  	}
  2753  	noTopics := func(topicList []*c.TopicsRow, pagi c.Paginator) {
  2754  		exf(len(topicList) == 0, "len(topicList) should be 0 not %d", len(topicList))
  2755  		testPagi(pagi, []int{}, 1, 1)
  2756  	}
  2757  	noTopicsOnPage2 := func(topicList []*c.TopicsRow, pagi c.Paginator) {
  2758  		exf(len(topicList) == 0, "len(topicList) should be 0 not %d", len(topicList))
  2759  		testPagi(pagi, []int{1}, 2, 1)
  2760  	}
  2761  
  2762  	forum, err := c.Forums.Get(fid)
  2763  	expectNilErr(t, err)
  2764  
  2765  	isAdmin, isMod, isBanned := false, false, false
  2766  	gid, err := c.Groups.Create("Topic List Test", "Test", isAdmin, isMod, isBanned)
  2767  	expectNilErr(t, err)
  2768  	ex(c.Groups.Exists(gid), "The group we just made doesn't exist")
  2769  
  2770  	fp, err := c.FPStore.GetCopy(fid, gid)
  2771  	if err == sql.ErrNoRows {
  2772  		fp = *c.BlankForumPerms()
  2773  	} else if err != nil {
  2774  		expectNilErr(t, err)
  2775  	}
  2776  	fp.ViewTopic = true
  2777  
  2778  	forum, err = c.Forums.Get(fid)
  2779  	expectNilErr(t, err)
  2780  	expectNilErr(t, forum.SetPerms(&fp, "custom", gid))
  2781  
  2782  	g, err := c.Groups.Get(gid)
  2783  	expectNilErr(t, err)
  2784  
  2785  	noTopicsTests := func() {
  2786  		rr := func(page, orderby int) {
  2787  			topicList, forumList, pagi, err := c.TopicList.GetListByGroup(g, page, orderby, []int{fid})
  2788  			expectNilErr(t, err)
  2789  			noTopics(topicList, pagi)
  2790  			exf(len(forumList) == 1, "len(forumList) should be 1 not %d", len(forumList))
  2791  		}
  2792  		rr(1, 0)
  2793  		rr(2, 0)
  2794  		rr(1, 1)
  2795  		rr(2, 1)
  2796  
  2797  		topicList, pagi, err := c.TopicList.GetListByForum(forum, 1, 0)
  2798  		expectNilErr(t, err)
  2799  		noTopics(topicList, pagi)
  2800  
  2801  		topicList, pagi, err = tint.RawGetListByForum(forum, 1, 0)
  2802  		expectNilErr(t, err)
  2803  		noTopics(topicList, pagi)
  2804  
  2805  		topicList, forumList, pagi, err := c.TopicList.GetList(1, 0, []int{fid})
  2806  		expectNilErr(t, err)
  2807  		noTopics(topicList, pagi)
  2808  		exf(len(forumList) == 1, "len(forumList) should be 1 not %d", len(forumList))
  2809  
  2810  		topicList, forumList, pagi, err = c.TopicList.GetListByCanSee([]int{fid}, 1, 0, []int{fid})
  2811  		expectNilErr(t, err)
  2812  		noTopics(topicList, pagi)
  2813  		exf(len(forumList) == 1, "len(forumList) should be 1 not %d", len(forumList))
  2814  
  2815  		topicList, forumList, pagi, err = c.TopicList.GetListByCanSee([]int{}, 1, 0, []int{fid})
  2816  		expectNilErr(t, err)
  2817  		noTopics(topicList, pagi)
  2818  		// TODO: Why is there a discrepency between this and GetList()?
  2819  		exf(len(forumList) == 0, "len(forumList) should be 0 not %d", len(forumList))
  2820  	}
  2821  	noTopicsTests()
  2822  
  2823  	tid, err := c.Topics.Create(fid, "New Topic", "New Topic Body", 1, "")
  2824  	expectNilErr(t, err)
  2825  
  2826  	topicList, forumList, pagi, err := c.TopicList.GetListByGroup(g, 1, 0, []int{fid})
  2827  	expectNilErr(t, err)
  2828  	test(topicList, pagi, 1, c.Paginator{[]int{1}, 1, 1}, tid)
  2829  	exf(len(forumList) == 1, "len(forumList) should be 1 not %d", len(forumList))
  2830  
  2831  	topicList, forumList, pagi, err = c.TopicList.GetListByGroup(g, 2, 0, []int{fid})
  2832  	expectNilErr(t, err)
  2833  	noTopics(topicList, pagi)
  2834  	exf(len(forumList) == 1, "len(forumList) should be 1 not %d", len(forumList))
  2835  
  2836  	topicList, forumList, pagi, err = c.TopicList.GetListByGroup(g, 1, 1, []int{fid})
  2837  	expectNilErr(t, err)
  2838  	test(topicList, pagi, 1, c.Paginator{[]int{1}, 1, 1}, tid)
  2839  	exf(len(forumList) == 1, "len(forumList) should be 1 not %d", len(forumList))
  2840  
  2841  	topicList, forumList, pagi, err = c.TopicList.GetListByGroup(g, 2, 1, []int{fid})
  2842  	expectNilErr(t, err)
  2843  	noTopicsOnPage2(topicList, pagi)
  2844  	exf(len(forumList) == 1, "len(forumList) should be 1 not %d", len(forumList))
  2845  
  2846  	topicList, pagi, err = tint.RawGetListByForum(forum, 1, 0)
  2847  	expectNilErr(t, err)
  2848  	test(topicList, pagi, 1, c.Paginator{[]int{1}, 1, 1}, tid)
  2849  
  2850  	topicList, pagi, err = tint.RawGetListByForum(forum, 0, 0)
  2851  	expectNilErr(t, err)
  2852  	test(topicList, pagi, 1, c.Paginator{[]int{1}, 1, 1}, tid)
  2853  
  2854  	expectNilErr(t, tint.Tick())
  2855  	forum, err = c.Forums.Get(fid)
  2856  	expectNilErr(t, err)
  2857  	topicList, pagi, err = c.TopicList.GetListByForum(forum, 1, 0)
  2858  	expectNilErr(t, err)
  2859  	test(topicList, pagi, 1, c.Paginator{[]int{1}, 1, 1}, tid)
  2860  
  2861  	topicList, forumList, pagi, err = c.TopicList.GetList(1, 0, []int{fid})
  2862  	expectNilErr(t, err)
  2863  	test(topicList, pagi, 1, c.Paginator{[]int{1}, 1, 1}, tid)
  2864  	exf(len(forumList) == 1, "len(forumList) should be 1 not %d", len(forumList))
  2865  
  2866  	topicList, forumList, pagi, err = c.TopicList.GetListByCanSee([]int{fid}, 1, 0, []int{fid})
  2867  	expectNilErr(t, err)
  2868  	test(topicList, pagi, 1, c.Paginator{[]int{1}, 1, 1}, tid)
  2869  	exf(len(forumList) == 1, "len(forumList) should be 1 not %d", len(forumList))
  2870  
  2871  	topicList, forumList, pagi, err = c.TopicList.GetListByCanSee([]int{}, 1, 0, []int{fid})
  2872  	expectNilErr(t, err)
  2873  	noTopics(topicList, pagi)
  2874  	exf(len(forumList) == 0, "len(forumList) should be 0 not %d", len(forumList))
  2875  
  2876  	topic, err := c.Topics.Get(tid)
  2877  	expectNilErr(t, err)
  2878  	expectNilErr(t, topic.Delete())
  2879  
  2880  	forum, err = c.Forums.Get(fid)
  2881  	expectNilErr(t, err)
  2882  	noTopicsTests()
  2883  
  2884  	// TODO: More tests
  2885  
  2886  	_ = ex
  2887  }
  2888  
  2889  func TestUtils(t *testing.T) {
  2890  	ee := func(email, eemail string) {
  2891  		cemail := c.CanonEmail(email)
  2892  		expectf(t, cemail == eemail, "%s should be %s", cemail, eemail)
  2893  	}
  2894  	ee("test@example.com", "test@example.com")
  2895  	ee("test.test@example.com", "test.test@example.com")
  2896  	ee("", "")
  2897  	ee("ddd", "ddd")
  2898  	ee("test.test@gmail.com", "testtest@gmail.com")
  2899  	ee("TEST.test@gmail.com", "testtest@gmail.com")
  2900  	ee("test.TEST.test@gmail.com", "testtesttest@gmail.com")
  2901  	ee("test..TEST.test@gmail.com", "testtesttest@gmail.com")
  2902  	ee("TEST.test@example.com", "test.test@example.com")
  2903  	ee("test.TEST.test@example.com", "test.test.test@example.com")
  2904  	// TODO: Exotic unicode email types? Are there those?
  2905  
  2906  	// TODO: More utils.go tests
  2907  }
  2908  
  2909  func TestWeakPassword(t *testing.T) {
  2910  	ex := exp(t)
  2911  	/*weakPass := func(password, name, email string) func(error,string,...interface{}) {
  2912  		err := c.WeakPassword(password, name, email)
  2913  		return func(expectErr error, m string, p ...interface{}) {
  2914  			m = fmt.Sprintf("pass=%s, user=%s, email=%s ", password, name, email) + m
  2915  			expect(t, err == expectErr, fmt.Sprintf(m,p...))
  2916  		}
  2917  	}*/
  2918  	nilErrStr := func(e error) error {
  2919  		if e == nil {
  2920  			e = errors.New("nil")
  2921  		}
  2922  		return e
  2923  	}
  2924  	weakPass := func(password, name, email string) func(error) {
  2925  		err := c.WeakPassword(password, name, email)
  2926  		e := nilErrStr(err)
  2927  		m := fmt.Sprintf("pass=%s, user=%s, email=%s ", password, name, email)
  2928  		return func(expectErr error) {
  2929  			ee := nilErrStr(expectErr)
  2930  			ex(err == expectErr, m+fmt.Sprintf("err should be '%s' not '%s'", ee, e))
  2931  		}
  2932  	}
  2933  
  2934  	//weakPass("test", "test", "test@example.com")(c.ErrWeakPasswordContains,"err should be ErrWeakPasswordContains not '%s'")
  2935  	weakPass("", "draw", "test@example.com")(c.ErrWeakPasswordNone)
  2936  	weakPass("test", "draw", "test@example.com")(c.ErrWeakPasswordShort)
  2937  	weakPass("testtest", "draw", "test@example.com")(c.ErrWeakPasswordContains)
  2938  	weakPass("testdraw", "draw", "test@example.com")(c.ErrWeakPasswordNameInPass)
  2939  	weakPass("test@example.com", "draw", "test@example.com")(c.ErrWeakPasswordEmailInPass)
  2940  	weakPass("meet@example.com2", "draw", "")(c.ErrWeakPasswordNoUpper)
  2941  	weakPass("Meet@example.com2", "draw", "")(nil)
  2942  	weakPass("test2", "draw", "test@example.com")(c.ErrWeakPasswordShort)
  2943  	weakPass("test22222222", "draw", "test@example.com")(c.ErrWeakPasswordContains)
  2944  	weakPass("superman", "draw", "test@example.com")(c.ErrWeakPasswordCommon)
  2945  	weakPass("Superman", "draw", "test@example.com")(c.ErrWeakPasswordCommon)
  2946  	weakPass("Superma2", "draw", "test@example.com")(nil)
  2947  	weakPass("superman2", "draw", "test@example.com")(c.ErrWeakPasswordCommon)
  2948  	weakPass("Superman2", "draw", "test@example.com")(c.ErrWeakPasswordCommon)
  2949  	weakPass("superman22", "draw", "test@example.com")(c.ErrWeakPasswordNoUpper)
  2950  	weakPass("K\\@<^s}1", "draw", "test@example.com")(nil)
  2951  	weakPass("K\\@<^s}r", "draw", "test@example.com")(c.ErrWeakPasswordNoNumbers)
  2952  	weakPass("k\\@<^s}1", "draw", "test@example.com")(c.ErrWeakPasswordNoUpper)
  2953  	weakPass("aaaaaaaa", "draw", "test@example.com")(c.ErrWeakPasswordNoUpper)
  2954  	weakPass("aA1aA1aA1", "draw", "test@example.com")(c.ErrWeakPasswordUniqueChars)
  2955  	weakPass("abababab", "draw", "test@example.com")(c.ErrWeakPasswordNoUpper)
  2956  	weakPass("11111111111111111111", "draw", "test@example.com")(c.ErrWeakPasswordNoUpper)
  2957  	weakPass("aaaaaaaaaaAAAAAAAAAA", "draw", "test@example.com")(c.ErrWeakPasswordUniqueChars)
  2958  	weakPass("-:u/nMxb,A!n=B;H\\sjM", "draw", "test@example.com")(nil)
  2959  }
  2960  
  2961  func TestAuth(t *testing.T) {
  2962  	ex := exp(t)
  2963  	// bcrypt likes doing stupid things, so this test will probably fail
  2964  	realPassword := "Madame Cassandra's Mystic Orb"
  2965  	t.Logf("Set realPassword to '%s'", realPassword)
  2966  	t.Log("Hashing the real password with bcrypt")
  2967  	hashedPassword, _, err := c.BcryptGeneratePassword(realPassword)
  2968  	if err != nil {
  2969  		t.Error(err)
  2970  	}
  2971  	passwordTest(t, realPassword, hashedPassword)
  2972  	// TODO: Peek at the prefix to verify this is a bcrypt hash
  2973  
  2974  	t.Log("Hashing the real password")
  2975  	hashedPassword2, _, err := c.GeneratePassword(realPassword)
  2976  	if err != nil {
  2977  		t.Error(err)
  2978  	}
  2979  	passwordTest(t, realPassword, hashedPassword2)
  2980  	// TODO: Peek at the prefix to verify this is a bcrypt hash
  2981  
  2982  	_, err, _ = c.Auth.Authenticate("None", "password")
  2983  	errmsg := "Name None shouldn't exist"
  2984  	if err != nil {
  2985  		errmsg += "\n" + err.Error()
  2986  	}
  2987  	ex(err == c.ErrNoUserByName, errmsg)
  2988  
  2989  	uid, err, _ := c.Auth.Authenticate("Admin", "password")
  2990  	expectNilErr(t, err)
  2991  	expectf(t, uid == 1, "Default admin uid should be 1 not %d", uid)
  2992  
  2993  	_, err, _ = c.Auth.Authenticate("Sam", "ReallyBadPassword")
  2994  	errmsg = "Name Sam shouldn't exist"
  2995  	if err != nil {
  2996  		errmsg += "\n" + err.Error()
  2997  	}
  2998  	ex(err == c.ErrNoUserByName, errmsg)
  2999  
  3000  	admin, err := c.Users.Get(1)
  3001  	expectNilErr(t, err)
  3002  	// TODO: Move this into the user store tests to provide better coverage? E.g. To see if the installer and the user creator initialise the field differently
  3003  	ex(admin.Session == "", "Admin session should be blank")
  3004  
  3005  	session, err := c.Auth.CreateSession(1)
  3006  	expectNilErr(t, err)
  3007  	ex(session != "", "Admin session shouldn't be blank")
  3008  	// TODO: Test the actual length set in the setting in addition to this "too short" test
  3009  	// TODO: We might be able to push up this minimum requirement
  3010  	ex(len(session) > 10, "Admin session shouldn't be too short")
  3011  	ex(admin.Session != session, "Old session should not match new one")
  3012  	admin, err = c.Users.Get(1)
  3013  	expectNilErr(t, err)
  3014  	ex(admin.Session == session, "Sessions should match")
  3015  
  3016  	// TODO: Create a user with a unicode password and see if we can login as them
  3017  	// TODO: Tests for SessionCheck, GetCookies, and ForceLogout
  3018  	// TODO: Tests for MFA Verification
  3019  }
  3020  
  3021  // TODO: Vary the salts? Keep in mind that some algorithms store the salt in the hash therefore the salt string may be blank
  3022  func passwordTest(t *testing.T, realPassword, hashedPassword string) {
  3023  	if len(hashedPassword) < 10 {
  3024  		t.Error("Hash too short")
  3025  	}
  3026  	salt := ""
  3027  	password := realPassword
  3028  	t.Logf("Testing password '%s'", password)
  3029  	t.Logf("Testing salt '%s'", salt)
  3030  	err := c.CheckPassword(hashedPassword, password, salt)
  3031  	if err == c.ErrMismatchedHashAndPassword {
  3032  		t.Error("The two don't match")
  3033  	} else if err == c.ErrPasswordTooLong {
  3034  		t.Error("CheckPassword thinks the password is too long")
  3035  	} else if err != nil {
  3036  		t.Error(err)
  3037  	}
  3038  
  3039  	password = "hahaha"
  3040  	t.Logf("Testing password '%s'", password)
  3041  	t.Logf("Testing salt '%s'", salt)
  3042  	err = c.CheckPassword(hashedPassword, password, salt)
  3043  	if err == c.ErrPasswordTooLong {
  3044  		t.Error("CheckPassword thinks the password is too long")
  3045  	} else if err == nil {
  3046  		t.Error("The two shouldn't match!")
  3047  	}
  3048  
  3049  	password = "Madame Cassandra's Mystic"
  3050  	t.Logf("Testing password '%s'", password)
  3051  	t.Logf("Testing salt '%s'", salt)
  3052  	err = c.CheckPassword(hashedPassword, password, salt)
  3053  	expect(t, err != c.ErrPasswordTooLong, "CheckPassword thinks the password is too long")
  3054  	expect(t, err != nil, "The two shouldn't match!")
  3055  }
  3056  
  3057  func TestUserPrivacy(t *testing.T) {
  3058  	pu, u := c.BlankUser(), &c.GuestUser
  3059  	pu.ID = 1
  3060  	ex, exf := exp(t), expf(t)
  3061  	ex(!pu.Privacy.NoPresence, "pu.Privacy.NoPresence should be false")
  3062  	ex(!u.Privacy.NoPresence, "u.Privacy.NoPresence should be false")
  3063  
  3064  	var msg string
  3065  	test := func(expects bool, level int) {
  3066  		pu.Privacy.ShowComments = level
  3067  		val := c.PrivacyCommentsShow(pu, u)
  3068  		var bit string
  3069  		if !expects {
  3070  			bit = " not"
  3071  			val = !val
  3072  		}
  3073  		exf(val, "%s should%s be able to see comments on level %d", msg, bit, level)
  3074  	}
  3075  	// 0 = default, 1 = public, 2 = registered, 3 = friends, 4 = self, 5 = disabled
  3076  
  3077  	msg = "guest users"
  3078  	test(true, 0)
  3079  	test(true, 1)
  3080  	test(false, 2)
  3081  	test(false, 3)
  3082  	test(false, 4)
  3083  	test(false, 5)
  3084  
  3085  	u = c.BlankUser()
  3086  	msg = "blank users"
  3087  	test(true, 0)
  3088  	test(true, 1)
  3089  	test(false, 2)
  3090  	//test(false,3)
  3091  	test(false, 4)
  3092  	test(false, 5)
  3093  
  3094  	u.Loggedin = true
  3095  	msg = "registered users"
  3096  	test(true, 0)
  3097  	test(true, 1)
  3098  	test(true, 2)
  3099  	test(false, 3)
  3100  	test(false, 4)
  3101  	test(false, 5)
  3102  
  3103  	u.IsBanned = true
  3104  	msg = "banned users"
  3105  	test(true, 0)
  3106  	test(true, 1)
  3107  	test(true, 2)
  3108  	test(false, 3)
  3109  	test(false, 4)
  3110  	test(false, 5)
  3111  	u.IsBanned = false
  3112  
  3113  	u.IsMod = true
  3114  	msg = "mods"
  3115  	test(true, 0)
  3116  	test(true, 1)
  3117  	test(true, 2)
  3118  	test(false, 3)
  3119  	test(false, 4)
  3120  	test(false, 5)
  3121  	u.IsMod = false
  3122  
  3123  	u.IsSuperMod = true
  3124  	msg = "super mods"
  3125  	test(true, 0)
  3126  	test(true, 1)
  3127  	test(true, 2)
  3128  	test(false, 3)
  3129  	test(false, 4)
  3130  	test(false, 5)
  3131  	u.IsSuperMod = false
  3132  
  3133  	u.IsAdmin = true
  3134  	msg = "admins"
  3135  	test(true, 0)
  3136  	test(true, 1)
  3137  	test(true, 2)
  3138  	test(false, 3)
  3139  	test(false, 4)
  3140  	test(false, 5)
  3141  	u.IsAdmin = false
  3142  
  3143  	u.IsSuperAdmin = true
  3144  	msg = "super admins"
  3145  	test(true, 0)
  3146  	test(true, 1)
  3147  	test(true, 2)
  3148  	test(false, 3)
  3149  	test(false, 4)
  3150  	test(false, 5)
  3151  	u.IsSuperAdmin = false
  3152  
  3153  	u.ID = 1
  3154  	test(true, 0)
  3155  	test(true, 1)
  3156  	test(true, 2)
  3157  	test(true, 3)
  3158  	test(true, 4)
  3159  	test(false, 5)
  3160  }
  3161  
  3162  type METri struct {
  3163  	Name    string // Optional, this is here for tests involving invisible characters so we know what's going in
  3164  	Msg     string
  3165  	Expects string
  3166  }
  3167  
  3168  type METriList struct {
  3169  	Items []METri
  3170  }
  3171  
  3172  func (l *METriList) Add(args ...string) {
  3173  	if len(args) < 2 {
  3174  		panic("need 2 or more args")
  3175  	}
  3176  	if len(args) > 2 {
  3177  		l.Items = append(l.Items, METri{args[0], args[1], args[2]})
  3178  	} else {
  3179  		l.Items = append(l.Items, METri{"", args[0], args[1]})
  3180  	}
  3181  }
  3182  
  3183  type CountTest struct {
  3184  	Name    string
  3185  	Msg     string
  3186  	Expects int
  3187  }
  3188  
  3189  type CountTestList struct {
  3190  	Items []CountTest
  3191  }
  3192  
  3193  func (l *CountTestList) Add(name, msg string, expects int) {
  3194  	l.Items = append(l.Items, CountTest{name, msg, expects})
  3195  }
  3196  
  3197  func TestWordCount(t *testing.T) {
  3198  	l := &CountTestList{nil}
  3199  	l.Add("blank", "", 0)
  3200  	l.Add("single-letter", "h", 1)
  3201  	l.Add("single-kana", "お", 1)
  3202  	l.Add("single-letter-words", "h h", 2)
  3203  	l.Add("two-letter", "h", 1)
  3204  	l.Add("two-kana", "おは", 1)
  3205  	l.Add("two-letter-words", "hh hh", 2)
  3206  	l.Add("", "h,h", 2)
  3207  	l.Add("", "h,,h", 2)
  3208  	l.Add("", "h, h", 2)
  3209  	l.Add("", "  h, h", 2)
  3210  	l.Add("", "h, h  ", 2)
  3211  	l.Add("", "  h, h  ", 2)
  3212  	l.Add("", "h,  h", 2)
  3213  	l.Add("", "h\nh", 2)
  3214  	l.Add("", "h\"h", 2)
  3215  	l.Add("", "h[r]h", 3)
  3216  	l.Add("", "お,お", 2)
  3217  	l.Add("", "γŠγ€γŠ", 2)
  3218  	l.Add("", "お\nお", 2)
  3219  	l.Add("", "γŠβ€γŠ", 2)
  3220  	l.Add("", "γŠγ€Œγ‚γ€γŠ", 3)
  3221  
  3222  	for _, item := range l.Items {
  3223  		res := c.WordCount(item.Msg)
  3224  		if res != item.Expects {
  3225  			if item.Name != "" {
  3226  				t.Error("Name: ", item.Name)
  3227  			}
  3228  			t.Error("Testing string '" + item.Msg + "'")
  3229  			t.Error("Bad output:", res)
  3230  			t.Error("Expected:", item.Expects)
  3231  		}
  3232  	}
  3233  }
  3234  
  3235  func TestTick(t *testing.T) {
  3236  	expectNilErr(t, c.StartupTasks())
  3237  	expectNilErr(t, c.Dailies())
  3238  
  3239  	expectNilErr(t, c.Tasks.HalfSec.Run())
  3240  	expectNilErr(t, c.Tasks.Sec.Run())
  3241  	expectNilErr(t, c.Tasks.FifteenMin.Run())
  3242  	expectNilErr(t, c.Tasks.Hour.Run())
  3243  	expectNilErr(t, c.Tasks.Day.Run())
  3244  
  3245  	thumbChan := make(chan bool)
  3246  	expectNilErr(t, tickLoop(thumbChan))
  3247  	expectNilErr(t, c.CTickLoop.HalfSecf())
  3248  	expectNilErr(t, c.CTickLoop.Secf())
  3249  	expectNilErr(t, c.CTickLoop.FifteenMinf())
  3250  	expectNilErr(t, c.CTickLoop.Hourf())
  3251  	expectNilErr(t, c.CTickLoop.Dayf())
  3252  }
  3253  
  3254  func TestWSHub(t *testing.T) {
  3255  	ex, exf, h := exp(t), expf(t), &c.WsHub
  3256  	exf(h.GuestCount() == 0, "GuestCount should be %d not %d", 0, h.GuestCount())
  3257  	exf(h.UserCount() == 0, "UserCount should be %d not %d", 0, h.UserCount())
  3258  	ex(!h.HasUser(-1), "HasUser(-1) should be false")
  3259  	ex(!h.HasUser(0), "HasUser(0) should be false")
  3260  	ex(!h.HasUser(1), "HasUser(1) should be false")
  3261  
  3262  	uid, e := c.Users.Create("WsHub Test", "WsHub Test", "", 1, true)
  3263  	expectNilErr(t, e)
  3264  	exf(!h.HasUser(uid), "HasUser(%d) should be false", uid)
  3265  	exf(len(h.AllUsers()) == 0, "len(AllUsers()) should be %d not %d", 0, len(h.AllUsers()))
  3266  
  3267  	f := func(uid, guestCount, userCount, allUserListLen int, hasUser bool) {
  3268  		exf(h.GuestCount() == guestCount, "GuestCount should be %d not %d", guestCount, h.GuestCount())
  3269  		exf(h.UserCount() == userCount, "UserCount should be %d not %d", userCount, h.UserCount())
  3270  		exf(len(h.AllUsers()) == allUserListLen, "len(AllUsers()) should be %d not %d", allUserListLen, len(h.AllUsers()))
  3271  		if hasUser {
  3272  			exf(h.HasUser(uid), "HasUser(%d) should be true", uid)
  3273  		} else {
  3274  			exf(!h.HasUser(uid), "HasUser(%d) should be false", uid)
  3275  		}
  3276  	}
  3277  
  3278  	u, e := c.Users.Get(uid)
  3279  	expectNilErr(t, e)
  3280  	wsUser, e := h.AddConn(u, nil)
  3281  	expectNilErr(t, e)
  3282  	f(uid, 0, 1, 1, true)
  3283  
  3284  	uid, e = c.Users.Create("WsHub Test 2", "WsHub Test 2", "", 1, true)
  3285  	expectNilErr(t, e)
  3286  	u2, e := c.Users.Get(uid)
  3287  	expectNilErr(t, e)
  3288  	wsUser2, e := h.AddConn(u2, nil)
  3289  	expectNilErr(t, e)
  3290  	f(uid, 0, 2, 2, true)
  3291  
  3292  	h.RemoveConn(wsUser2, nil)
  3293  	f(uid, 0, 1, 1, false)
  3294  	h.RemoveConn(wsUser2, nil)
  3295  	f(uid, 0, 1, 1, false)
  3296  	h.RemoveConn(wsUser, nil)
  3297  	f(uid, 0, 0, 0, false)
  3298  
  3299  	countSockets := func(wsUser *c.WSUser, expect int) {
  3300  		exf(wsUser.CountSockets() == expect, "CountSockets() should be %d not %d", expect, wsUser.CountSockets())
  3301  	}
  3302  	wsUser2, e = h.AddConn(u2, nil)
  3303  	expectNilErr(t, e)
  3304  	f(uid, 0, 1, 1, true)
  3305  	countSockets(wsUser2, 1)
  3306  	wsUser2.RemoveSocket(nil)
  3307  	f(uid, 0, 1, 1, true)
  3308  	countSockets(wsUser2, 0)
  3309  	h.RemoveConn(wsUser2, nil)
  3310  	f(uid, 0, 0, 0, false)
  3311  	countSockets(wsUser2, 0)
  3312  
  3313  	wsUser2, e = h.AddConn(u2, nil)
  3314  	expectNilErr(t, e)
  3315  	f(uid, 0, 1, 1, true)
  3316  	countSockets(wsUser2, 1)
  3317  	expectNilErr(t, wsUser2.Ping())
  3318  	f(uid, 0, 1, 1, true)
  3319  	countSockets(wsUser2, 0)
  3320  	h.RemoveConn(wsUser2, nil)
  3321  	f(uid, 0, 0, 0, false)
  3322  	countSockets(wsUser2, 0)
  3323  
  3324  	// TODO: Add more tests
  3325  }