db_test.go

  1package user
  2
  3import (
  4	"database/sql"
  5	"testing"
  6	"time"
  7
  8	_ "github.com/mattn/go-sqlite3"
  9	"github.com/stretchr/testify/assert"
 10)
 11
 12func getDB() (*sql.DB, func()) {
 13	db, err := sql.Open("sqlite3", "file:test.db?cache=shared&mode=memory&_fk=false")
 14	if err != nil {
 15		panic(err)
 16	}
 17	if err := Migrate(db); err != nil {
 18		panic(err)
 19	}
 20	return db, func() { db.Close() }
 21}
 22
 23func TestEmail(t *testing.T) {
 24	db, close := getDB()
 25	defer close()
 26
 27	e1 := Email("[email protected]")
 28	e2 := Email("[email protected]")
 29
 30	// insert email with mixed case
 31	assert.NoError(t, Set(db, int64(0), e1))
 32	// violate unique lowercase index
 33	assert.Error(t, Set(db, int64(1), e2))
 34	// upsert different email value (lowercase) when conflict on id
 35	assert.NoError(t, Set(db, int64(0), e2))
 36
 37	er, _, err := GetEmailAndHandle(db, int64(0))
 38	assert.NoError(t, err)
 39	assert.Equal(t, "[email protected]", er)
 40}
 41
 42func TestHandle(t *testing.T) {
 43	db, close := getDB()
 44	defer close()
 45
 46	h1 := Handle("MFer")
 47	h2 := Handle("mfer")
 48	h3 := Handle("scooter")
 49
 50	assert.NoError(t, Set(db, int64(0), h1))
 51	// violate unique constraint
 52	assert.Error(t, Set(db, int64(1), h2))
 53	// blocklist
 54	assert.Error(t, Set(db, int64(3), h3))
 55
 56	assert.ErrorIs(t, CheckHandle(db, "MFer"), ErrHandleExists)
 57	assert.ErrorIs(t, CheckHandle(db, "mfer"), ErrHandleExists)
 58	assert.NoError(t, CheckHandle(db, "test"))
 59}
 60
 61func TestHandleValidity(t *testing.T) {
 62	tc := map[string]bool{
 63		"scooter":            false,
 64		"rtw.run":            false,
 65		"test":               true,
 66		"a":                  false,
 67		"AAA":                true,
 68		"test.com":           true,
 69		"something.net/path": true,
 70		"me...":              true,
 71		"also-me":            true,
 72		"something;":         false,
 73		".":                  false,
 74		"..":                 true,
 75		"[deleted]":          false,
 76		"[DELETED]":          false,
 77		"MODERATOR":          false,
 78		"@twitter":           true,
 79		"test![yo]":          true,
 80	}
 81	for handle, isOK := range tc {
 82		t.Run(handle, func(t *testing.T) {
 83			ok := isHandleValid(handle)
 84			assert.Equal(t, isOK, ok)
 85		})
 86	}
 87}
 88
 89func TestMetadata(t *testing.T) {
 90	db, close := getDB()
 91	defer close()
 92
 93	// Covers to the two most common cases for additional metadata
 94
 95	// Case 1: map[string]T
 96	in1 := map[string]string{"test": "test1", "test2": "test"}
 97	out1 := map[string]string{}
 98	assert.NoError(t, Set(db, int64(0), Metadata(in1)))
 99	assert.NoError(t, GetMetadata(db, int64(0), &out1))
100	assert.Equal(t, in1, out1)
101
102	// Case 2: struct marshaled to JSON
103	type testStruct struct {
104		Name  string
105		Value int
106	}
107	in2 := testStruct{"test", 1}
108	out2 := testStruct{}
109	assert.NoError(t, Set(db, int64(1), Metadata(in2)))
110	assert.NoError(t, GetMetadata(db, int64(1), &out2))
111	assert.Equal(t, in2, out2)
112}
113
114func TestCheckEmail(t *testing.T) {
115	db, close := getDB()
116	defer close()
117
118	h1 := Email("[email protected]")
119	h2 := Email("[email protected]")
120
121	assert.NoError(t, Set(db, int64(0), h1))
122	// violate unique constraint
123	assert.Error(t, Set(db, int64(1), h2))
124
125	assert.ErrorIs(t, CheckEmail(db, "[email protected]"), ErrAccountExists)
126	assert.ErrorIs(t, CheckEmail(db, "[email protected]"), ErrAccountExists)
127	assert.ErrorIs(t, CheckEmail(db, "notanemail"), ErrInvalidEmail)
128	assert.NoError(t, CheckEmail(db, "[email protected]"))
129}
130
131func TestLogin(t *testing.T) {
132	db, close := getDB()
133	defer close()
134
135	assert.NoError(t, Set(db, int64(0), Email("[email protected]"), Password("pa55w0rd"), Handle("test")))
136	id, err := Login[int64](db, "[email protected]", "pa55w0rd")
137	assert.NoError(t, err)
138	assert.Equal(t, int64(0), id)
139
140	id, err = Login[int64](db, "test", "pa55w0rd")
141	assert.NoError(t, err)
142	assert.Equal(t, int64(0), id)
143
144	id, err = Login[int64](db, "test", "garbage")
145	assert.Equal(t, id, int64(-1))
146	assert.ErrorIs(t, err, ErrPasswordMismatch)
147}
148
149func TestCleanup(t *testing.T) {
150	db, close := getDB()
151	defer close()
152
153	// create anon account, no handle, no email
154	assert.NoError(t, Set(db, int64(0)))
155	// manually backdate creation time more than a month
156	_, err := db.Exec("UPDATE users SET ctime=? WHERE id=0", time.Now().UTC().Add(-31*24*time.Hour))
157	assert.NoError(t, err)
158	// insert again to trigger
159	assert.NoError(t, Set(db, int64(1)))
160
161	var numRows int
162	err = db.QueryRow("SELECT COUNT(*) FROM users WHERE id=?", int64(0)).Scan(&numRows)
163	// row id 0 deleted
164	assert.NoError(t, err)
165	assert.Equal(t, 0, numRows)
166
167	// row id 1 remains
168	err = db.QueryRow("SELECT COUNT(*) FROM users").Scan(&numRows)
169	assert.NoError(t, err)
170	assert.Equal(t, 1, numRows)
171
172}