Refactoring (#325)
* Refactor core * Re-added tests * Small fixes * Add tests for acmetxt cidrslice and util funcs * Remove the last dangling reference to old logging package * Refactoring (#327) * chore: enable more linters and fix linter issues * ci: enable linter checks on all branches and disable recurring checks recurring linter checks don't make that much sense. The code & linter checks should not change on their own over night ;) * chore: update packages * Revert "chore: update packages" This reverts commit 30250bf28c4b39e9e5b3af012a4e28ab036bf9af. * chore: manually upgrade some packages * Updated dependencies, wrote changelog entry and fixed namespace for release * Refactoring - improving coverage (#371) * Increase code coverage in acmedns * More testing of ReadConfig() and its fallback mechanism * Found that if someone put a '"' double quote into the filename that we configure zap to log to, it would cause the the JSON created to be invalid. I have replaced the JSON string with proper config * Better handling of config options for api.TLS - we now error on an invalid value instead of silently failing. added a basic test for api.setupTLS() (to increase test coverage) * testing nameserver isOwnChallenge and isAuthoritative methods * add a unit test for nameserver answerOwnChallenge * fix linting errors * bump go and golangci-lint versions in github actions * Update golangci-lint.yml Bumping github-actions workflow versions to accommodate some changes in upstream golanci-lint * Bump Golang version to 1.23 (currently the oldest supported version) Bump golanglint-ci to 2.0.2 and migrate the config file. This should resolve the math/rand/v2 issue * bump golanglint-ci action version * Fixing up new golanglint-ci warnings and errors --------- Co-authored-by: Joona Hoikkala <5235109+joohoi@users.noreply.github.com> * Minor refactoring, error returns and e2e testing suite * Add a few tests * Fix linter and umask setting * Update github actions * Refine concurrency configuration for GitHub actions * HTTP timeouts to API, and self-validation mutex to nameserver ops --------- Co-authored-by: Florian Ritterhoff <32478819+fritterhoff@users.noreply.github.com> Co-authored-by: Jason Playne <jason@jasonplayne.com>
This commit is contained in:
co-authored by
Florian Ritterhoff
Jason Playne
parent
b7a0a8a7bc
commit
5a7bc230b8
@@ -0,0 +1,361 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
_ "github.com/glebarez/go-sqlite"
|
||||
_ "github.com/lib/pq"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"go.uber.org/zap"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
|
||||
"github.com/joohoi/acme-dns/pkg/acmedns"
|
||||
)
|
||||
|
||||
type acmednsdb struct {
|
||||
DB *sql.DB
|
||||
Mutex sync.Mutex
|
||||
Logger *zap.SugaredLogger
|
||||
Config *acmedns.AcmeDnsConfig
|
||||
}
|
||||
|
||||
// DBVersion shows the database version this code uses. This is used for update checks.
|
||||
var DBVersion = 1
|
||||
|
||||
var acmeTable = `
|
||||
CREATE TABLE IF NOT EXISTS acmedns(
|
||||
Name TEXT,
|
||||
Value TEXT
|
||||
);`
|
||||
|
||||
var userTable = `
|
||||
CREATE TABLE IF NOT EXISTS records(
|
||||
Username TEXT UNIQUE NOT NULL PRIMARY KEY,
|
||||
Password TEXT UNIQUE NOT NULL,
|
||||
Subdomain TEXT UNIQUE NOT NULL,
|
||||
AllowFrom TEXT
|
||||
);`
|
||||
|
||||
var txtTable = `
|
||||
CREATE TABLE IF NOT EXISTS txt(
|
||||
Subdomain TEXT NOT NULL,
|
||||
Value TEXT NOT NULL DEFAULT '',
|
||||
LastUpdate INT
|
||||
);`
|
||||
|
||||
var txtTablePG = `
|
||||
CREATE TABLE IF NOT EXISTS txt(
|
||||
rowid SERIAL,
|
||||
Subdomain TEXT NOT NULL,
|
||||
Value TEXT NOT NULL DEFAULT '',
|
||||
LastUpdate INT
|
||||
);`
|
||||
|
||||
// getSQLiteStmt replaces all PostgreSQL prepared statement placeholders (eg. $1, $2) with SQLite variant "?"
|
||||
func getSQLiteStmt(s string) string {
|
||||
re, _ := regexp.Compile(`\$[0-9]`)
|
||||
return re.ReplaceAllString(s, "?")
|
||||
}
|
||||
|
||||
func Init(config *acmedns.AcmeDnsConfig, logger *zap.SugaredLogger) (acmedns.AcmednsDB, error) {
|
||||
var d = &acmednsdb{Config: config, Logger: logger}
|
||||
d.Mutex.Lock()
|
||||
defer d.Mutex.Unlock()
|
||||
db, err := sql.Open(config.Database.Engine, config.Database.Connection)
|
||||
if err != nil {
|
||||
return d, err
|
||||
}
|
||||
d.DB = db
|
||||
// Check version first to try to catch old versions without version string
|
||||
var versionString string
|
||||
_ = d.DB.QueryRow("SELECT Value FROM acmedns WHERE Name='db_version'").Scan(&versionString)
|
||||
if versionString == "" {
|
||||
versionString = "0"
|
||||
}
|
||||
_, _ = d.DB.Exec(acmeTable)
|
||||
_, _ = d.DB.Exec(userTable)
|
||||
if config.Database.Engine == "sqlite" {
|
||||
_, _ = d.DB.Exec(txtTable)
|
||||
} else {
|
||||
_, _ = d.DB.Exec(txtTablePG)
|
||||
}
|
||||
// If everything is fine, handle db upgrade tasks
|
||||
if err == nil {
|
||||
err = d.checkDBUpgrades(versionString)
|
||||
}
|
||||
if err == nil {
|
||||
if versionString == "0" {
|
||||
// No errors so we should now be in version 1
|
||||
insversion := fmt.Sprintf("INSERT INTO acmedns (Name, Value) values('db_version', '%d')", DBVersion)
|
||||
_, err = db.Exec(insversion)
|
||||
}
|
||||
}
|
||||
return d, err
|
||||
}
|
||||
|
||||
func (d *acmednsdb) checkDBUpgrades(versionString string) error {
|
||||
var err error
|
||||
version, err := strconv.Atoi(versionString)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if version != DBVersion {
|
||||
return d.handleDBUpgrades(version)
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func (d *acmednsdb) handleDBUpgrades(version int) error {
|
||||
if version == 0 {
|
||||
return d.handleDBUpgradeTo1()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *acmednsdb) handleDBUpgradeTo1() error {
|
||||
var err error
|
||||
var subdomains []string
|
||||
rows, err := d.DB.Query("SELECT Subdomain FROM records")
|
||||
if err != nil {
|
||||
d.Logger.Errorw("Error in DB upgrade",
|
||||
"error", err.Error())
|
||||
return err
|
||||
}
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
var subdomain string
|
||||
err = rows.Scan(&subdomain)
|
||||
if err != nil {
|
||||
d.Logger.Errorw("Error in DB upgrade while reading values",
|
||||
"error", err.Error())
|
||||
return err
|
||||
}
|
||||
subdomains = append(subdomains, subdomain)
|
||||
}
|
||||
err = rows.Err()
|
||||
if err != nil {
|
||||
d.Logger.Errorw("Error in DB upgrade while inserting values",
|
||||
"error", err.Error())
|
||||
return err
|
||||
}
|
||||
tx, err := d.DB.Begin()
|
||||
// Rollback if errored, commit if not
|
||||
defer func() {
|
||||
if err != nil {
|
||||
_ = tx.Rollback()
|
||||
return
|
||||
}
|
||||
_ = tx.Commit()
|
||||
}()
|
||||
_, _ = tx.Exec("DELETE FROM txt")
|
||||
for _, subdomain := range subdomains {
|
||||
if subdomain != "" {
|
||||
// Insert two rows for each subdomain to txt table
|
||||
err = d.NewTXTValuesInTransaction(tx, subdomain)
|
||||
if err != nil {
|
||||
d.Logger.Errorw("Error in DB upgrade while inserting values",
|
||||
"error", err.Error())
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
// SQLite doesn't support dropping columns
|
||||
if d.Config.Database.Engine != "sqlite" {
|
||||
_, _ = tx.Exec("ALTER TABLE records DROP COLUMN IF EXISTS Value")
|
||||
_, _ = tx.Exec("ALTER TABLE records DROP COLUMN IF EXISTS LastActive")
|
||||
}
|
||||
_, err = tx.Exec("UPDATE acmedns SET Value='1' WHERE Name='db_version'")
|
||||
return err
|
||||
}
|
||||
|
||||
// NewTXTValuesInTransaction creates two rows for subdomain to the txt table
|
||||
func (d *acmednsdb) NewTXTValuesInTransaction(tx *sql.Tx, subdomain string) error {
|
||||
var err error
|
||||
instr := fmt.Sprintf("INSERT INTO txt (Subdomain, LastUpdate) values('%s', 0)", subdomain)
|
||||
_, _ = tx.Exec(instr)
|
||||
_, _ = tx.Exec(instr)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *acmednsdb) Register(afrom acmedns.Cidrslice) (acmedns.ACMETxt, error) {
|
||||
d.Mutex.Lock()
|
||||
defer d.Mutex.Unlock()
|
||||
var err error
|
||||
tx, err := d.DB.Begin()
|
||||
// Rollback if errored, commit if not
|
||||
defer func() {
|
||||
if err != nil {
|
||||
_ = tx.Rollback()
|
||||
return
|
||||
}
|
||||
_ = tx.Commit()
|
||||
}()
|
||||
a := acmedns.NewACMETxt()
|
||||
a.AllowFrom = acmedns.Cidrslice(afrom.ValidEntries())
|
||||
passwordHash, err := bcrypt.GenerateFromPassword([]byte(a.Password), 10)
|
||||
regSQL := `
|
||||
INSERT INTO records(
|
||||
Username,
|
||||
Password,
|
||||
Subdomain,
|
||||
AllowFrom)
|
||||
values($1, $2, $3, $4)`
|
||||
if d.Config.Database.Engine == "sqlite" {
|
||||
regSQL = getSQLiteStmt(regSQL)
|
||||
}
|
||||
sm, err := tx.Prepare(regSQL)
|
||||
if err != nil {
|
||||
d.Logger.Errorw("Database error in prepare",
|
||||
"error", err.Error())
|
||||
return a, fmt.Errorf("failed to prepare registration statement: %w", err)
|
||||
}
|
||||
defer sm.Close()
|
||||
_, err = sm.Exec(a.Username.String(), passwordHash, a.Subdomain, a.AllowFrom.JSON())
|
||||
if err == nil {
|
||||
err = d.NewTXTValuesInTransaction(tx, a.Subdomain)
|
||||
}
|
||||
return a, err
|
||||
}
|
||||
|
||||
func (d *acmednsdb) GetByUsername(u uuid.UUID) (acmedns.ACMETxt, error) {
|
||||
d.Mutex.Lock()
|
||||
defer d.Mutex.Unlock()
|
||||
var results []acmedns.ACMETxt
|
||||
getSQL := `
|
||||
SELECT Username, Password, Subdomain, AllowFrom
|
||||
FROM records
|
||||
WHERE Username=$1 LIMIT 1
|
||||
`
|
||||
if d.Config.Database.Engine == "sqlite" {
|
||||
getSQL = getSQLiteStmt(getSQL)
|
||||
}
|
||||
|
||||
sm, err := d.DB.Prepare(getSQL)
|
||||
if err != nil {
|
||||
return acmedns.ACMETxt{}, err
|
||||
}
|
||||
defer sm.Close()
|
||||
rows, err := sm.Query(u.String())
|
||||
if err != nil {
|
||||
return acmedns.ACMETxt{}, fmt.Errorf("failed to query user: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
// It will only be one row though
|
||||
for rows.Next() {
|
||||
txt, err := d.getModelFromRow(rows)
|
||||
if err != nil {
|
||||
return acmedns.ACMETxt{}, err
|
||||
}
|
||||
results = append(results, txt)
|
||||
}
|
||||
if len(results) > 0 {
|
||||
return results[0], nil
|
||||
}
|
||||
return acmedns.ACMETxt{}, fmt.Errorf("user not found: %s", u.String())
|
||||
}
|
||||
|
||||
func (d *acmednsdb) GetTXTForDomain(domain string) ([]string, error) {
|
||||
d.Mutex.Lock()
|
||||
defer d.Mutex.Unlock()
|
||||
domain = acmedns.SanitizeString(domain)
|
||||
var txts []string
|
||||
getSQL := `
|
||||
SELECT Value FROM txt WHERE Subdomain=$1 LIMIT 2
|
||||
`
|
||||
if d.Config.Database.Engine == "sqlite" {
|
||||
getSQL = getSQLiteStmt(getSQL)
|
||||
}
|
||||
|
||||
sm, err := d.DB.Prepare(getSQL)
|
||||
if err != nil {
|
||||
return txts, err
|
||||
}
|
||||
defer sm.Close()
|
||||
rows, err := sm.Query(domain)
|
||||
if err != nil {
|
||||
return txts, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
var rtxt string
|
||||
err = rows.Scan(&rtxt)
|
||||
if err != nil {
|
||||
return txts, err
|
||||
}
|
||||
txts = append(txts, rtxt)
|
||||
}
|
||||
return txts, nil
|
||||
}
|
||||
|
||||
func (d *acmednsdb) Update(a acmedns.ACMETxtPost) error {
|
||||
d.Mutex.Lock()
|
||||
defer d.Mutex.Unlock()
|
||||
var err error
|
||||
// Data in a is already sanitized
|
||||
timenow := time.Now().Unix()
|
||||
|
||||
updSQL := `
|
||||
UPDATE txt SET Value=$1, LastUpdate=$2
|
||||
WHERE rowid=(
|
||||
SELECT rowid FROM txt WHERE Subdomain=$3 ORDER BY LastUpdate LIMIT 1)
|
||||
`
|
||||
if d.Config.Database.Engine == "sqlite" {
|
||||
updSQL = getSQLiteStmt(updSQL)
|
||||
}
|
||||
|
||||
sm, err := d.DB.Prepare(updSQL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer sm.Close()
|
||||
_, err = sm.Exec(a.Value, timenow, a.Subdomain)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *acmednsdb) getModelFromRow(r *sql.Rows) (acmedns.ACMETxt, error) {
|
||||
txt := acmedns.ACMETxt{}
|
||||
afrom := ""
|
||||
err := r.Scan(
|
||||
&txt.Username,
|
||||
&txt.Password,
|
||||
&txt.Subdomain,
|
||||
&afrom)
|
||||
if err != nil {
|
||||
d.Logger.Errorw("Row scan error",
|
||||
"error", err.Error())
|
||||
}
|
||||
|
||||
cslice := acmedns.Cidrslice{}
|
||||
err = json.Unmarshal([]byte(afrom), &cslice)
|
||||
if err != nil {
|
||||
d.Logger.Errorw("JSON unmarshall error",
|
||||
"error", err.Error())
|
||||
}
|
||||
txt.AllowFrom = cslice
|
||||
return txt, err
|
||||
}
|
||||
|
||||
func (d *acmednsdb) Close() {
|
||||
d.DB.Close()
|
||||
}
|
||||
|
||||
func (d *acmednsdb) GetBackend() *sql.DB {
|
||||
return d.DB
|
||||
}
|
||||
|
||||
func (d *acmednsdb) SetBackend(backend *sql.DB) {
|
||||
d.DB = backend
|
||||
}
|
||||
@@ -0,0 +1,308 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/erikstmartin/go-testdb"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/joohoi/acme-dns/pkg/acmedns"
|
||||
)
|
||||
|
||||
type testResult struct {
|
||||
lastID int64
|
||||
affectedRows int64
|
||||
}
|
||||
|
||||
func (r testResult) LastInsertId() (int64, error) {
|
||||
return r.lastID, nil
|
||||
}
|
||||
|
||||
func (r testResult) RowsAffected() (int64, error) {
|
||||
return r.affectedRows, nil
|
||||
}
|
||||
|
||||
func fakeConfigAndLogger() (acmedns.AcmeDnsConfig, *zap.SugaredLogger) {
|
||||
c := acmedns.AcmeDnsConfig{}
|
||||
c.Database.Engine = "sqlite"
|
||||
c.Database.Connection = ":memory:"
|
||||
l := zap.NewNop().Sugar()
|
||||
return c, l
|
||||
}
|
||||
|
||||
func fakeDB() acmedns.AcmednsDB {
|
||||
conf, logger := fakeConfigAndLogger()
|
||||
db, _ := Init(&conf, logger)
|
||||
return db
|
||||
}
|
||||
|
||||
func TestRegisterNoCIDR(t *testing.T) {
|
||||
// Register tests
|
||||
DB := fakeDB()
|
||||
_, err := DB.Register(acmedns.Cidrslice{})
|
||||
if err != nil {
|
||||
t.Errorf("Registration failed, got error [%v]", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegisterMany(t *testing.T) {
|
||||
DB := fakeDB()
|
||||
for i, test := range []struct {
|
||||
input acmedns.Cidrslice
|
||||
output acmedns.Cidrslice
|
||||
}{
|
||||
{acmedns.Cidrslice{"127.0.0.1/8", "8.8.8.8/32", "1.0.0.1/1"}, acmedns.Cidrslice{"127.0.0.1/8", "8.8.8.8/32", "1.0.0.1/1"}},
|
||||
{acmedns.Cidrslice{"1.1.1./32", "1922.168.42.42/8", "1.1.1.1/33", "1.2.3.4/"}, acmedns.Cidrslice{}},
|
||||
{acmedns.Cidrslice{"7.6.5.4/32", "invalid", "1.0.0.1/2"}, acmedns.Cidrslice{"7.6.5.4/32", "1.0.0.1/2"}},
|
||||
} {
|
||||
user, err := DB.Register(test.input)
|
||||
if err != nil {
|
||||
t.Errorf("Test %d: Got error from register method: [%v]", i, err)
|
||||
}
|
||||
res, err := DB.GetByUsername(user.Username)
|
||||
if err != nil {
|
||||
t.Errorf("Test %d: Got error when fetching username: [%v]", i, err)
|
||||
}
|
||||
if len(user.AllowFrom) != len(test.output) {
|
||||
t.Errorf("Test %d: Expected to receive struct with [%d] entries in AllowFrom, but got [%d] records", i, len(test.output), len(user.AllowFrom))
|
||||
}
|
||||
if len(res.AllowFrom) != len(test.output) {
|
||||
t.Errorf("Test %d: Expected to receive struct with [%d] entries in AllowFrom, but got [%d] records", i, len(test.output), len(res.AllowFrom))
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetByUsername(t *testing.T) {
|
||||
DB := fakeDB()
|
||||
// Create reg to refer to
|
||||
reg, err := DB.Register(acmedns.Cidrslice{})
|
||||
if err != nil {
|
||||
t.Errorf("Registration failed, got error [%v]", err)
|
||||
}
|
||||
|
||||
regUser, err := DB.GetByUsername(reg.Username)
|
||||
if err != nil {
|
||||
t.Errorf("Could not get test user, got error [%v]", err)
|
||||
}
|
||||
|
||||
if reg.Username != regUser.Username {
|
||||
t.Errorf("GetByUsername username [%q] did not match the original [%q]", regUser.Username, reg.Username)
|
||||
}
|
||||
|
||||
if reg.Subdomain != regUser.Subdomain {
|
||||
t.Errorf("GetByUsername subdomain [%q] did not match the original [%q]", regUser.Subdomain, reg.Subdomain)
|
||||
}
|
||||
|
||||
// regUser password already is a bcrypt hash
|
||||
if !acmedns.CorrectPassword(reg.Password, regUser.Password) {
|
||||
t.Errorf("The password [%s] does not match the hash [%s]", reg.Password, regUser.Password)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrepareErrors(t *testing.T) {
|
||||
DB := fakeDB()
|
||||
reg, _ := DB.Register(acmedns.Cidrslice{})
|
||||
tdb, err := sql.Open("testdb", "")
|
||||
if err != nil {
|
||||
t.Errorf("Got error: %v", err)
|
||||
}
|
||||
oldDb := DB.GetBackend()
|
||||
DB.SetBackend(tdb)
|
||||
defer DB.SetBackend(oldDb)
|
||||
defer testdb.Reset()
|
||||
|
||||
_, err = DB.GetByUsername(reg.Username)
|
||||
if err == nil {
|
||||
t.Errorf("Expected error, but didn't get one")
|
||||
}
|
||||
|
||||
_, err = DB.GetTXTForDomain(reg.Subdomain)
|
||||
if err == nil {
|
||||
t.Errorf("Expected error, but didn't get one")
|
||||
}
|
||||
}
|
||||
|
||||
func TestQueryExecErrors(t *testing.T) {
|
||||
DB := fakeDB()
|
||||
reg, _ := DB.Register(acmedns.Cidrslice{})
|
||||
testdb.SetExecWithArgsFunc(func(query string, args []driver.Value) (result driver.Result, err error) {
|
||||
return testResult{1, 0}, errors.New("Prepared query error")
|
||||
})
|
||||
|
||||
testdb.SetQueryWithArgsFunc(func(query string, args []driver.Value) (result driver.Rows, err error) {
|
||||
columns := []string{"Username", "Password", "Subdomain", "Value", "LastActive"}
|
||||
return testdb.RowsFromSlice(columns, [][]driver.Value{}), errors.New("Prepared query error")
|
||||
})
|
||||
|
||||
defer testdb.Reset()
|
||||
|
||||
tdb, err := sql.Open("testdb", "")
|
||||
if err != nil {
|
||||
t.Errorf("Got error: %v", err)
|
||||
}
|
||||
oldDb := DB.GetBackend()
|
||||
|
||||
DB.SetBackend(tdb)
|
||||
defer DB.SetBackend(oldDb)
|
||||
|
||||
_, err = DB.GetByUsername(reg.Username)
|
||||
if err == nil {
|
||||
t.Errorf("Expected error from exec, but got none")
|
||||
}
|
||||
|
||||
_, err = DB.GetTXTForDomain(reg.Subdomain)
|
||||
if err == nil {
|
||||
t.Errorf("Expected error from exec in GetByDomain, but got none")
|
||||
}
|
||||
|
||||
_, err = DB.Register(acmedns.Cidrslice{})
|
||||
if err == nil {
|
||||
t.Errorf("Expected error from exec in Register, but got none")
|
||||
}
|
||||
reg.Value = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||
err = DB.Update(reg.ACMETxtPost)
|
||||
if err == nil {
|
||||
t.Errorf("Expected error from exec in Update, but got none")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestQueryScanErrors(t *testing.T) {
|
||||
DB := fakeDB()
|
||||
reg, _ := DB.Register(acmedns.Cidrslice{})
|
||||
|
||||
testdb.SetExecWithArgsFunc(func(query string, args []driver.Value) (result driver.Result, err error) {
|
||||
return testResult{1, 0}, errors.New("Prepared query error")
|
||||
})
|
||||
|
||||
testdb.SetQueryWithArgsFunc(func(query string, args []driver.Value) (result driver.Rows, err error) {
|
||||
columns := []string{"Only one"}
|
||||
resultrows := "this value"
|
||||
return testdb.RowsFromCSVString(columns, resultrows), nil
|
||||
})
|
||||
|
||||
defer testdb.Reset()
|
||||
tdb, err := sql.Open("testdb", "")
|
||||
if err != nil {
|
||||
t.Errorf("Got error: %v", err)
|
||||
}
|
||||
oldDb := DB.GetBackend()
|
||||
|
||||
DB.SetBackend(tdb)
|
||||
defer DB.SetBackend(oldDb)
|
||||
|
||||
_, err = DB.GetByUsername(reg.Username)
|
||||
if err == nil {
|
||||
t.Errorf("Expected error from scan in, but got none")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBadDBValues(t *testing.T) {
|
||||
DB := fakeDB()
|
||||
reg, _ := DB.Register(acmedns.Cidrslice{})
|
||||
|
||||
testdb.SetQueryWithArgsFunc(func(query string, args []driver.Value) (result driver.Rows, err error) {
|
||||
columns := []string{"Username", "Password", "Subdomain", "Value", "LastActive"}
|
||||
resultrows := "invalid,invalid,invalid,invalid,"
|
||||
return testdb.RowsFromCSVString(columns, resultrows), nil
|
||||
})
|
||||
|
||||
defer testdb.Reset()
|
||||
tdb, err := sql.Open("testdb", "")
|
||||
if err != nil {
|
||||
t.Errorf("Got error: %v", err)
|
||||
}
|
||||
oldDb := DB.GetBackend()
|
||||
|
||||
DB.SetBackend(tdb)
|
||||
defer DB.SetBackend(oldDb)
|
||||
|
||||
_, err = DB.GetByUsername(reg.Username)
|
||||
if err == nil {
|
||||
t.Errorf("Expected error from scan in, but got none")
|
||||
}
|
||||
|
||||
_, err = DB.GetTXTForDomain(reg.Subdomain)
|
||||
if err == nil {
|
||||
t.Errorf("Expected error from scan in GetByDomain, but got none")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetTXTForDomain(t *testing.T) {
|
||||
DB := fakeDB()
|
||||
// Create reg to refer to
|
||||
reg, err := DB.Register(acmedns.Cidrslice{})
|
||||
if err != nil {
|
||||
t.Errorf("Registration failed, got error [%v]", err)
|
||||
}
|
||||
|
||||
txtval1 := "___validation_token_received_from_the_ca___"
|
||||
txtval2 := "___validation_token_received_YEAH_the_ca___"
|
||||
|
||||
reg.Value = txtval1
|
||||
_ = DB.Update(reg.ACMETxtPost)
|
||||
|
||||
reg.Value = txtval2
|
||||
_ = DB.Update(reg.ACMETxtPost)
|
||||
|
||||
regDomainSlice, err := DB.GetTXTForDomain(reg.Subdomain)
|
||||
if err != nil {
|
||||
t.Errorf("Could not get test user, got error [%v]", err)
|
||||
}
|
||||
if len(regDomainSlice) == 0 {
|
||||
t.Errorf("No rows returned for GetTXTForDomain [%s]", reg.Subdomain)
|
||||
}
|
||||
|
||||
var val1found = false
|
||||
var val2found = false
|
||||
for _, v := range regDomainSlice {
|
||||
if v == txtval1 {
|
||||
val1found = true
|
||||
}
|
||||
if v == txtval2 {
|
||||
val2found = true
|
||||
}
|
||||
}
|
||||
if !val1found {
|
||||
t.Errorf("No TXT value found for val1")
|
||||
}
|
||||
if !val2found {
|
||||
t.Errorf("No TXT value found for val2")
|
||||
}
|
||||
|
||||
// Not found
|
||||
regNotfound, _ := DB.GetTXTForDomain("does-not-exist")
|
||||
if len(regNotfound) > 0 {
|
||||
t.Errorf("No records should be returned.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdate(t *testing.T) {
|
||||
DB := fakeDB()
|
||||
// Create reg to refer to
|
||||
reg, err := DB.Register(acmedns.Cidrslice{})
|
||||
if err != nil {
|
||||
t.Errorf("Registration failed, got error [%v]", err)
|
||||
}
|
||||
|
||||
regUser, err := DB.GetByUsername(reg.Username)
|
||||
if err != nil {
|
||||
t.Errorf("Could not get test user, got error [%v]", err)
|
||||
}
|
||||
|
||||
// Set new values (only TXT should be updated) (matches by username and subdomain)
|
||||
|
||||
validTXT := "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||
|
||||
regUser.Password = "nevergonnagiveyouup"
|
||||
regUser.Value = validTXT
|
||||
|
||||
err = DB.Update(regUser.ACMETxtPost)
|
||||
if err != nil {
|
||||
t.Errorf("DB Update failed, got error: [%v]", err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user