1
0
Fork 0
mirror of https://github.com/Eggbertx/gochan.git synced 2025-08-28 08:06:24 -07:00

Add sqlmock tests

This commit is contained in:
Eggbertx 2024-03-28 23:04:22 -07:00
parent 02e1533d09
commit 1417695dcc
9 changed files with 312 additions and 39 deletions

1
go.mod
View file

@ -30,6 +30,7 @@ require (
require (
filippo.io/edwards25519 v1.1.0 // indirect
github.com/DATA-DOG/go-sqlmock v1.5.2
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect

3
go.sum
View file

@ -3,6 +3,8 @@ filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/CuberL/glua-async v0.0.0-20190614102843-43f22221106d h1:lGZoWyUkx8m8e0LsP9hSv0zcK/f5ffJEEYo+B0RIJFM=
github.com/CuberL/glua-async v0.0.0-20190614102843-43f22221106d/go.mod h1:9LzvPuiPyVXXFvHZCKOCvXxy1KkFADRmePZJDFuuFgE=
github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU=
github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU=
github.com/Eggbertx/durationutil v1.0.0 h1:/DJo6z/9tac/1KTwa/RGjpg5Q8Rrnx3p5w4OxKdeTkw=
github.com/Eggbertx/durationutil v1.0.0/go.mod h1:eOfcV0W5B4qoKsEtjhOYUTsTm6KnrWVkfgOmMTC4XnY=
github.com/VividCortex/ewma v1.1.1/go.mod h1:2Tkkvm3sRDVXaiyucHiACn4cqf7DpdyLvmxzcbUokwA=
@ -76,6 +78,7 @@ github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=

View file

@ -378,3 +378,30 @@ func SetVersion(version string) {
cfg.Version = ParseVersion(version)
}
}
// SetTestDBConfig sets up the database configuration for a testing environment. If it is not run via `go test`,
// it will silently return without changing anything
func SetTestDBConfig(dbType string, dbHost string, dbName string, dbUsername string, dbPassword string, dbPrefix string) {
if !strings.HasSuffix(os.Args[0], ".test") {
// only run in a testing environment
return
}
if cfg == nil {
cfg = defaultGochanConfig
}
cfg.DBtype = dbType
cfg.DBhost = dbHost
cfg.DBname = dbName
cfg.DBusername = dbUsername
cfg.DBpassword = dbPassword
cfg.DBprefix = dbPrefix
}
// SetRandomSeed is usd to set a deterministic seed to make testing easier
func SetRandomSeed(seed string) {
if !strings.HasSuffix(os.Args[0], ".test") {
// only run in a testing environment
return
}
cfg.RandomSeed = seed
}

View file

@ -23,7 +23,39 @@ const (
sqlite3ConnStr = "file:%s?_auth&_auth_user=%s&_auth_pass=%s&_auth_crypt=sha1"
)
var gcdb *GCDB
var (
gcdb *GCDB
mysqlReplacerArr = []string{
"RANGE_START_ATON", "INET6_ATON(range_start)",
"RANGE_START_NTOA", "INET6_NTOA(range_start)",
"RANGE_END_ATON", "INET6_ATON(range_end)",
"RANGE_END_NTOA", "INET6_NTOA(range_end)",
"IP_ATON", "INET6_ATON(ip)",
"IP_NTOA", "INET6_NTOA(ip)",
"PARAM_ATON", "INET6_ATON(?)",
"PARAM_NTOA", "INET6_NTOA(?)",
}
postgresReplacerArr = []string{
"RANGE_START_ATON", "range_start",
"RANGE_START_NTOA", "range_start",
"RANGE_END_ATON", "range_end",
"RANGE_END_NTOA", "range_end",
"IP_ATON", "ip",
"IP_NTOA", "ip",
"PARAM_ATON", "?",
"PARAM_NTOA", "?",
}
sqlite3ReplacerArr = []string{
"RANGE_START_ATON", "range_start",
"RANGE_START_NTOA", "range_start",
"RANGE_END_ATON", "range_end",
"RANGE_END_NTOA", "range_end",
"IP_ATON", "ip",
"IP_NTOA", "ip",
"PARAM_ATON", "?",
"PARAM_NTOA", "?",
}
)
type GCDB struct {
db *sql.DB
@ -217,7 +249,7 @@ func (db *GCDB) QueryTxSQL(tx *sql.Tx, query string, a ...interface{}) (*sql.Row
return stmt.Query(a...)
}
func Open(host, dbDriver, dbName, username, password, prefix string) (db *GCDB, err error) {
func setupDBConn(host, dbDriver, dbName, username, password, prefix string) (db *GCDB, err error) {
db = &GCDB{
driver: dbDriver,
}
@ -229,48 +261,26 @@ func Open(host, dbDriver, dbName, username, password, prefix string) (db *GCDB,
switch dbDriver {
case "mysql":
db.connStr = fmt.Sprintf(mysqlConnStr, username, password, host, dbName)
replacerArr = append(replacerArr,
"RANGE_START_ATON", "INET6_ATON(range_start)",
"RANGE_START_NTOA", "INET6_NTOA(range_start)",
"RANGE_END_ATON", "INET6_ATON(range_end)",
"RANGE_END_NTOA", "INET6_NTOA(range_end)",
"IP_ATON", "INET6_ATON(ip)",
"IP_NTOA", "INET6_NTOA(ip)",
"PARAM_ATON", "INET6_ATON(?)",
"PARAM_NTOA", "INET6_NTOA(?)",
)
replacerArr = append(replacerArr, mysqlReplacerArr...)
case "postgres":
db.connStr = fmt.Sprintf(postgresConnStr, username, password, host, dbName)
replacerArr = append(replacerArr,
"RANGE_START_ATON", "range_start",
"RANGE_START_NTOA", "range_start",
"RANGE_END_ATON", "range_end",
"RANGE_END_NTOA", "range_end",
"IP_ATON", "ip",
"IP_NTOA", "ip",
"PARAM_ATON", "?",
"PARAM_NTOA", "?",
)
replacerArr = append(replacerArr, postgresReplacerArr...)
case "sqlite3":
addrMatches := tcpHostIsolator.FindAllStringSubmatch(host, -1)
if len(addrMatches) > 0 && len(addrMatches[0]) > 2 {
host = addrMatches[0][2]
}
db.connStr = fmt.Sprintf(sqlite3ConnStr, host, username, password)
replacerArr = append(replacerArr,
"RANGE_START_ATON", "range_start",
"RANGE_START_NTOA", "range_start",
"RANGE_END_ATON", "range_end",
"RANGE_END_NTOA", "range_end",
"IP_ATON", "ip",
"IP_NTOA", "ip",
"PARAM_ATON", "?",
"PARAM_NTOA", "?",
)
replacerArr = append(replacerArr, sqlite3ReplacerArr...)
default:
return nil, ErrUnsupportedDB
}
db.replacer = strings.NewReplacer(replacerArr...)
return db, nil
}
func Open(host, dbDriver, dbName, username, password, prefix string) (db *GCDB, err error) {
db, err = setupDBConn(host, dbDriver, dbName, username, password, prefix)
db.db, err = sql.Open(db.driver, db.connStr)
if err != nil {
db.db.SetConnMaxLifetime(time.Minute * 3)

79
pkg/gcsql/open_test.go Normal file
View file

@ -0,0 +1,79 @@
package gcsql
import (
"testing"
"github.com/DATA-DOG/go-sqlmock"
"github.com/stretchr/testify/assert"
)
func closeMock(t *testing.T, mock sqlmock.Sqlmock) {
t.Helper()
if gcdb == nil || gcdb.db == nil || mock == nil {
return
}
mock.ExpectClose()
assert.NoError(t, Close())
err := mock.ExpectationsWereMet()
assert.NoError(t, err)
}
func initMock(t *testing.T, dbDriver string) (sqlmock.Sqlmock, error) {
t.Helper()
err := Close()
assert.NoError(t, err)
gcdb, err = setupDBConn("localhost", dbDriver, "gochan", "gochan", "gochan", "")
if !assert.NoError(t, err) {
return nil, err
}
var mock sqlmock.Sqlmock
gcdb.db, mock, err = sqlmock.New()
return mock, err
}
func TestOpenMySQL(t *testing.T) {
var err error
gcdb, err = setupDBConn("localhost", "mysql", "gochan", "gochan", "gochan", "")
if !assert.NoError(t, err) {
return
}
var mock sqlmock.Sqlmock
gcdb.db, mock, err = sqlmock.New()
assert.NoError(t, err)
defer closeMock(t, mock)
}
func TestOpenPostgres(t *testing.T) {
var err error
gcdb, err = setupDBConn("localhost", "postgres", "gochan", "gochan", "gochan", "")
if !assert.NoError(t, err) {
return
}
var mock sqlmock.Sqlmock
gcdb.db, mock, err = sqlmock.New()
assert.NoError(t, err)
defer closeMock(t, mock)
}
func TestOpenSQLite3(t *testing.T) {
var err error
gcdb, err = setupDBConn("localhost", "sqlite3", "gochan", "gochan", "gochan", "")
if !assert.NoError(t, err) {
return
}
var mock sqlmock.Sqlmock
gcdb.db, mock, err = sqlmock.New()
assert.NoError(t, err)
defer closeMock(t, mock)
}
func TestOpenUnrecognizedDriver(t *testing.T) {
assert.NoError(t, Close())
_, err := setupDBConn("localhost", "wat", "gochan", "gochan", "gochan", "")
assert.Error(t, err)
}

View file

@ -0,0 +1,35 @@
package gcsql
import (
"testing"
"github.com/DATA-DOG/go-sqlmock"
"github.com/gochan-org/gochan/pkg/config"
"github.com/stretchr/testify/assert"
)
func TestProvisionMySQL(t *testing.T) {
_, err := goToGochanRoot(t)
if !assert.NoError(t, err) {
return
}
config.SetVersion("3.10.1")
config.SetRandomSeed("test")
config.SetTestDBConfig("mysql", "localhost", "gochan", "gochan", "gochan", "")
gcdb, err = setupDBConn("localhost", "mysql", "gochan", "gochan", "gochan", "")
if !assert.NoError(t, err) {
return
}
var mock sqlmock.Sqlmock
gcdb.db, mock, err = sqlmock.New()
if !assert.NoError(t, err) {
return
}
if !assert.NoError(t, setupGochanMockDB(t, mock, "gochan", "mysql")) {
return
}
closeMock(t, mock)
}

View file

@ -1,6 +1,9 @@
package gcsql
import "database/sql"
import (
"database/sql"
"errors"
)
var (
// AllSections provides a quick and simple way to access a list of all non-hidden sections without
@ -41,7 +44,7 @@ func getOrCreateDefaultSectionID() (sectionID int, err error) {
const query = `SELECT id FROM DBPREFIXsections WHERE name = 'Main'`
var id int
err = QueryRowSQL(query, interfaceSlice(), interfaceSlice(&id))
if err == sql.ErrNoRows {
if errors.Is(err, sql.ErrNoRows) {
var section *Section
if section, err = NewSection("Main", "main", false, -1); err != nil {
return 0, err
@ -96,7 +99,11 @@ func NewSection(name string, abbreviation string, hidden bool, position int) (*S
// position not specified
stmt2, err := PrepareSQL(sqlPosition, tx)
if err != nil {
return nil, err
if errors.Is(err, sql.ErrNoRows) {
position = 1
} else {
return nil, err
}
}
if err = stmt2.QueryRow().Scan(&position); err != nil {
return nil, err

View file

@ -16,13 +16,16 @@ var (
// createDefaultAdminIfNoStaff creates a new default admin account if no accounts exist
func createDefaultAdminIfNoStaff() error {
const sql = `SELECT COUNT(id) FROM DBPREFIXstaff`
const query = `SELECT COUNT(id) FROM DBPREFIXstaff`
var count int
QueryRowSQL(sql, interfaceSlice(), interfaceSlice(&count))
err := QueryRowSQL(query, interfaceSlice(), interfaceSlice(&count))
if err != nil && !errors.Is(err, sql.ErrNoRows) {
return err
}
if count > 0 {
return nil
}
_, err := NewStaff("admin", "password", 3)
_, err = NewStaff("admin", "password", 3)
return err
}
@ -32,7 +35,7 @@ func NewStaff(username string, password string, rank int) (*Staff, error) {
VALUES(?,?,?)`
passwordChecksum := gcutil.BcryptSum(password)
_, err := ExecSQL(sqlINSERT, username, passwordChecksum, rank)
if err != nil {
if err != nil && !errors.Is(err, sql.ErrNoRows) {
return nil, err
}
return &Staff{

108
pkg/gcsql/util_test.go Normal file
View file

@ -0,0 +1,108 @@
package gcsql
import (
"database/sql"
"database/sql/driver"
"errors"
"os"
"path"
"testing"
"github.com/DATA-DOG/go-sqlmock"
)
var (
testInitDBMySQLStatements = []string{
`CREATE TABLE database_version\(\s+component VARCHAR\(40\) NOT NULL PRIMARY KEY,\s+version INT NOT NULL \)`,
`CREATE TABLE sections\(\s+id BIGINT NOT NULL AUTO_INCREMENT UNIQUE PRIMARY KEY,\s+name TEXT NOT NULL,\s+abbreviation TEXT NOT NULL,\s+position SMALLINT NOT NULL,\s+hidden BOOL NOT NULL \)`,
`CREATE TABLE boards\(\s*id BIGINT NOT NULL AUTO_INCREMENT UNIQUE PRIMARY KEY,\s+section_id BIGINT NOT NULL,\s+uri VARCHAR\(45\) NOT NULL,\s+dir VARCHAR\(45\) NOT NULL,\s+navbar_position SMALLINT NOT NULL,\s+title VARCHAR\(45\) NOT NULL,\s+subtitle VARCHAR\(64\) NOT NULL,\s+description VARCHAR\(64\) NOT NULL,\s+max_file_size INT NOT NULL,\s+max_threads SMALLINT NOT NULL, default_style VARCHAR\(45\) NOT NULL,\s+locked BOOL NOT NULL,\s+created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,\s+anonymous_name VARCHAR\(45\) NOT NULL DEFAULT 'Anonymous',\s+force_anonymous BOOL NOT NULL,\s+autosage_after SMALLINT NOT NULL,\s+no_images_after SMALLINT NOT NULL,\s+max_message_length SMALLINT NOT NULL,\s+min_message_length SMALLINT NOT NULL,\s+allow_embeds BOOL NOT NULL,\s+redirect_to_thread BOOL NOT NULL,\s+require_file BOOL NOT NULL,\s+enable_catalog BOOL NOT NULL,\s+CONSTRAINT boards_section_id_fk\s+FOREIGN KEY\(section_id\) REFERENCES sections\(id\),\s+CONSTRAINT boards_dir_unique UNIQUE\(dir\),\s+CONSTRAINT boards_uri_unique UNIQUE\(uri\)\s*\)`,
`CREATE TABLE threads\(\s*id BIGINT NOT NULL AUTO_INCREMENT UNIQUE PRIMARY KEY,\s+board_id BIGINT NOT NULL,\s+locked BOOL NOT NULL DEFAULT FALSE,\s+stickied BOOL NOT NULL DEFAULT FALSE,\s+anchored BOOL NOT NULL DEFAULT FALSE,\s+cyclical BOOL NOT NULL DEFAULT FALSE,\s+last_bump TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,\s+deleted_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,\s+is_deleted BOOL NOT NULL DEFAULT FALSE,\s+CONSTRAINT threads_board_id_fk\s+FOREIGN KEY\(board_id\) REFERENCES boards\(id\) ON DELETE CASCADE\s*\)`,
`CREATE INDEX thread_deleted_index ON threads\(is_deleted\)`,
`CREATE TABLE posts\(\s+id BIGINT NOT NULL AUTO_INCREMENT UNIQUE PRIMARY KEY,\s+thread_id BIGINT NOT NULL,\s+is_top_post BOOL NOT NULL DEFAULT FALSE,\s+ip VARBINARY\(16\) NOT NULL,\s+created_on TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,\s+name VARCHAR\(50\) NOT NULL DEFAULT '',\s+tripcode VARCHAR\(10\) NOT NULL DEFAULT '',\s+is_role_signature BOOL NOT NULL DEFAULT FALSE, email VARCHAR\(50\) NOT NULL DEFAULT '',\s+subject VARCHAR\(100\) NOT NULL DEFAULT '',\s+message TEXT NOT NULL,\s+message_raw TEXT NOT NULL,\s+password TEXT NOT NULL,\s+deleted_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,\s+is_deleted BOOL NOT NULL DEFAULT FALSE,\s+banned_message TEXT,\s+flag VARCHAR\(45\) NOT NULL DEFAULT '',\s+country VARCHAR\(80\) NOT NULL DEFAULT '',\s+CONSTRAINT posts_thread_id_fk\s+FOREIGN KEY\(thread_id\) REFERENCES threads\(id\) ON DELETE CASCADE \)`,
`CREATE INDEX top_post_index ON posts\(is_top_post\)`,
`CREATE TABLE files\(\s+id BIGINT NOT NULL AUTO_INCREMENT UNIQUE PRIMARY KEY,\s+post_id BIGINT NOT NULL,\s+file_order INT NOT NULL,\s+original_filename VARCHAR\(255\) NOT NULL,\s+filename VARCHAR\(45\) NOT NULL,\s+checksum TEXT NOT NULL,\s+file_size INT NOT NULL,\s+is_spoilered BOOL NOT NULL,\s+thumbnail_width INT NOT NULL,\s+thumbnail_height INT NOT NULL,\s+width INT NOT NULL,\s+height INT NOT NULL,\s+CONSTRAINT files_post_id_fk\s+FOREIGN KEY\(post_id\) REFERENCES posts\(id\) ON DELETE CASCADE,\s+CONSTRAINT files_post_id_file_order_unique UNIQUE\(post_id, file_order\) \)`,
`CREATE TABLE staff\(\s+id BIGINT NOT NULL AUTO_INCREMENT UNIQUE PRIMARY KEY,\s+username VARCHAR\(45\) NOT NULL,\s+password_checksum VARCHAR\(120\) NOT NULL,\s+global_rank INT,\s+added_on TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,\s+last_login TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,\s+is_active BOOL NOT NULL DEFAULT TRUE,\s+CONSTRAINT staff_username_unique UNIQUE\(username\) \)`,
`CREATE TABLE sessions\(\s+id BIGINT NOT NULL AUTO_INCREMENT UNIQUE PRIMARY KEY,\s+staff_id BIGINT NOT NULL,\s+expires TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,\s+data VARCHAR\(45\) NOT NULL,\s+CONSTRAINT sessions_staff_id_fk\s+FOREIGN KEY\(staff_id\) REFERENCES staff\(id\) ON DELETE CASCADE \)`,
`CREATE TABLE board_staff\(\s+board_id BIGINT NOT NULL,\s+staff_id BIGINT NOT NULL, CONSTRAINT board_staff_board_id_fk\s+FOREIGN KEY\(board_id\) REFERENCES boards\(id\) ON DELETE CASCADE,\s+CONSTRAINT board_staff_staff_id_fk\s+FOREIGN KEY\(staff_id\) REFERENCES staff\(id\) ON DELETE CASCADE,\s+CONSTRAINT board_staff_pk PRIMARY KEY \(board_id,staff_id\) \)`,
`CREATE TABLE announcements\(\s+id BIGINT NOT NULL AUTO_INCREMENT UNIQUE PRIMARY KEY,\s+staff_id BIGINT NOT NULL,\s+subject VARCHAR\(45\) NOT NULL,\s+message TEXT NOT NULL,\s+timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,\s+CONSTRAINT announcements_staff_id_fk FOREIGN KEY\(staff_id\) REFERENCES staff\(id\) \)`,
`CREATE TABLE ip_ban\(\s+id BIGINT NOT NULL AUTO_INCREMENT UNIQUE PRIMARY KEY,\s+staff_id BIGINT NOT NULL, board_id BIGINT, banned_for_post_id BIGINT, copy_post_text TEXT NOT NULL, is_thread_ban BOOL NOT NULL, is_active BOOL NOT NULL, range_start VARBINARY\(16\) NOT NULL, range_end VARBINARY\(16\) NOT NULL, issued_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, appeal_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, expires_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, permanent BOOL NOT NULL, staff_note VARCHAR\(255\) NOT NULL, message TEXT NOT NULL, can_appeal BOOL NOT NULL, CONSTRAINT ip_ban_board_id_fk FOREIGN KEY\(board_id\) REFERENCES boards\(id\) ON DELETE CASCADE, CONSTRAINT ip_ban_staff_id_fk FOREIGN KEY\(staff_id\) REFERENCES staff\(id\), CONSTRAINT ip_ban_banned_for_post_id_fk FOREIGN KEY\(banned_for_post_id\) REFERENCES posts\(id\) ON DELETE SET NULL \)`,
`CREATE TABLE ip_ban_audit\(\s+ip_ban_id BIGINT NOT NULL,\s+timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,\s+staff_id BIGINT NOT NULL,\s+is_active BOOL NOT NULL,\s+is_thread_ban BOOL NOT NULL,\s+expires_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,\s+appeal_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,\s+permanent BOOL NOT NULL,\s+staff_note VARCHAR\(255\) NOT NULL,\s+message TEXT NOT NULL,\s+can_appeal BOOL NOT NULL,\s+PRIMARY KEY\(ip_ban_id, timestamp\),\s+CONSTRAINT ip_ban_audit_ip_ban_id_fk\s+FOREIGN KEY\(ip_ban_id\) REFERENCES ip_ban\(id\) ON DELETE CASCADE,\s+CONSTRAINT ip_ban_audit_staff_id_fk\s+FOREIGN KEY\(staff_id\) REFERENCES staff\(id\)\s+\)`,
`CREATE TABLE ip_ban_appeals\(\s+id BIGINT NOT NULL AUTO_INCREMENT UNIQUE PRIMARY KEY,\s+staff_id BIGINT,\s+ip_ban_id BIGINT NOT NULL,\s+appeal_text TEXT NOT NULL,\s+staff_response TEXT,\s+is_denied BOOL NOT NULL,\s+CONSTRAINT ip_ban_appeals_staff_id_fk\s+FOREIGN KEY\(staff_id\) REFERENCES staff\(id\),\s+CONSTRAINT ip_ban_appeals_ip_ban_id_fk\s+FOREIGN KEY\(ip_ban_id\) REFERENCES ip_ban\(id\) ON DELETE CASCADE \)`,
`CREATE TABLE ip_ban_appeals_audit\(\s+appeal_id BIGINT NOT NULL,\s+timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,\s+staff_id BIGINT,\s+appeal_text TEXT NOT NULL,\s+staff_response TEXT,\s+is_denied BOOL NOT NULL,\s+PRIMARY KEY\(appeal_id, timestamp\),\s+CONSTRAINT ip_ban_appeals_audit_staff_id_fk\s+FOREIGN KEY\(staff_id\) REFERENCES staff\(id\),\s+CONSTRAINT ip_ban_appeals_audit_appeal_id_fk\s+FOREIGN KEY\(appeal_id\) REFERENCES ip_ban_appeals\(id\)\s+ON DELETE CASCADE \)`,
`CREATE TABLE reports\(\s+id BIGINT NOT NULL AUTO_INCREMENT UNIQUE PRIMARY KEY,\s+handled_by_staff_id BIGINT,\s+post_id BIGINT NOT NULL,\s+ip VARBINARY\(16\) NOT NULL,\s+reason TEXT NOT NULL,\s+is_cleared BOOL NOT NULL,\s+CONSTRAINT reports_handled_by_staff_id_fk\s+FOREIGN KEY\(handled_by_staff_id\) REFERENCES staff\(id\), CONSTRAINT reports_post_id_fk\s+FOREIGN KEY\(post_id\) REFERENCES posts\(id\) ON DELETE CASCADE \)`,
`CREATE TABLE reports_audit\(\s+report_id BIGINT NOT NULL,\s+timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,\s+handled_by_staff_id BIGINT,\s+is_cleared BOOL NOT NULL,\s+CONSTRAINT reports_audit_handled_by_staff_id_fk\s+FOREIGN KEY\(handled_by_staff_id\) REFERENCES staff\(id\),\s+CONSTRAINT reports_audit_report_id_fk\s+FOREIGN KEY\(report_id\) REFERENCES reports\(id\) ON DELETE CASCADE\s+\)`,
`CREATE TABLE filename_ban\(\s+id BIGINT NOT NULL AUTO_INCREMENT UNIQUE PRIMARY KEY,\s+board_id BIGINT,\s+staff_id BIGINT NOT NULL,\s+staff_note VARCHAR\(255\) NOT NULL,\s+issued_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,\s+filename VARCHAR\(255\) NOT NULL,\s+is_regex BOOL NOT NULL,\s+CONSTRAINT filename_ban_board_id_fk\s+FOREIGN KEY\(board_id\) REFERENCES boards\(id\) ON DELETE CASCADE,\s+CONSTRAINT filename_ban_staff_id_fk\s+FOREIGN KEY\(staff_id\) REFERENCES staff\(id\) \)`,
`CREATE TABLE username_ban\(\s+id BIGINT NOT NULL AUTO_INCREMENT UNIQUE PRIMARY KEY,\s+board_id BIGINT,\s+staff_id BIGINT NOT NULL,\s+staff_note VARCHAR\(255\) NOT NULL,\s+issued_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,\s+username VARCHAR\(255\) NOT NULL,\s+is_regex BOOL NOT NULL,\s+CONSTRAINT username_ban_board_id_fk\s+FOREIGN KEY\(board_id\) REFERENCES boards\(id\) ON DELETE CASCADE,\s+CONSTRAINT username_ban_staff_id_fk\s+FOREIGN KEY\(staff_id\) REFERENCES staff\(id\) \)`,
`CREATE TABLE file_ban\(\s+id BIGINT NOT NULL AUTO_INCREMENT UNIQUE PRIMARY KEY,\s+board_id BIGINT,\s+staff_id BIGINT NOT NULL,\s+staff_note VARCHAR\(255\) NOT NULL,\s+issued_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,\s+checksum TEXT NOT NULL,\s+fingerprinter VARCHAR\(64\),\s+ban_ip BOOL NOT NULL,\s+ban_ip_message TEXT,\s+CONSTRAINT file_ban_board_id_fk\s+FOREIGN KEY\(board_id\) REFERENCES boards\(id\) ON DELETE CASCADE,\s+CONSTRAINT file_ban_staff_id_fk\s+FOREIGN KEY\(staff_id\) REFERENCES staff\(id\)\s+\)`,
`CREATE TABLE wordfilters\(\s+id BIGINT NOT NULL AUTO_INCREMENT UNIQUE PRIMARY KEY,\s+board_dirs VARCHAR\(255\) DEFAULT '\*',\s+staff_id BIGINT NOT NULL,\s+staff_note VARCHAR\(255\) NOT NULL,\s+issued_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,\s+search VARCHAR\(75\) NOT NULL,\s+is_regex BOOL NOT NULL,\s+change_to VARCHAR\(75\) NOT NULL,\s+CONSTRAINT wordfilters_staff_id_fk\s+FOREIGN KEY\(staff_id\) REFERENCES staff\(id\),\s+CONSTRAINT wordfilters_search_check CHECK \(search <> ''\) \)`,
`INSERT INTO database_version\(component, version\)\s+VALUES\('gochan', 3\)`,
}
)
// TODO: move this to a dedicated gochan-specific testing utility package
func goToGochanRoot(t *testing.T) (string, error) {
t.Helper()
dir, err := os.Getwd()
if err != nil {
return "", err
}
for d := 0; d < 6; d++ {
if path.Base(dir) == "gochan" {
return dir, nil
}
if err = os.Chdir(".."); err != nil {
return dir, err
}
if dir, err = os.Getwd(); err != nil {
return dir, err
}
}
return dir, errors.New("test running from unexpected dir, should be in gochan root or the current testing dir")
}
func setupGochanMockDB(t *testing.T, mock sqlmock.Sqlmock, dbName string, dbType string) error {
t.Helper()
mock.ExpectPrepare("CREATE DATABASE " + dbName).
ExpectExec().WillReturnResult(driver.ResultNoRows)
mock.ExpectBegin()
for _, stmtStr := range testInitDBMySQLStatements {
mock.ExpectPrepare(stmtStr).
ExpectExec().WithoutArgs().
WillReturnResult(driver.ResultNoRows)
}
mock.ExpectCommit()
mock.ExpectPrepare(`SELECT COUNT\(\w+\) FROM staff`).
ExpectQuery().
WillReturnRows(sqlmock.NewRows([]string{}))
mock.ExpectPrepare(`INSERT INTO staff\s+\(username, password_checksum, global_rank\)\s+VALUES\(\?,\?,\?\)`).
ExpectExec().
WithArgs("admin", sqlmock.AnyArg(), 3).
WillReturnResult(driver.ResultNoRows)
mock.ExpectPrepare(`SELECT id FROM sections WHERE name = 'Main'`).
ExpectQuery().WithoutArgs().WillReturnError(sql.ErrNoRows)
mock.ExpectBegin()
mock.ExpectPrepare(`INSERT INTO sections \(name, abbreviation, hidden, position\) VALUES \(\?,\?,\?,\?\)`).
ExpectExec().WithArgs("Main", "main", false, 1).WillReturnResult(driver.ResultNoRows)
mock.ExpectPrepare(`SELECT COALESCE\(MAX\(position\) \+ 1, 1\) FROM sections`).
ExpectQuery().WillReturnRows(sqlmock.NewRows([]string{"1"}))
_, err := ExecSQL("CREATE DATABASE gochan")
if err != nil {
return err
}
if err = buildNewDatabase(dbType); err != nil {
return err
}
mock.ExpectationsWereMet()
return nil
}