Files
go-dbhub/dbhub_test.go
Martin Kleusberg 34238e7a0c Reduce dependencies on common code in test cases
This reduces the amount of dependencies on code in the dbhub.io/common
module in the test cases. The only remaining place where it is used is
for the diff function. Eventually this should also be reworked so we can
completely eliminate the import of the common module and have an
independent test here (i.e. not asking the server to do something, then
calling server code to verify its behaviour).
2024-01-16 22:54:22 +01:00

644 lines
19 KiB
Go

package dbhub
import (
"bytes"
"crypto/tls"
"io"
"log"
"math/rand"
"net/http"
"os"
"path/filepath"
"testing"
"time"
"github.com/docker/docker/pkg/fileutils"
sqlite "github.com/gwenn/gosqlite"
"github.com/sqlitebrowser/dbhub.io/common"
"github.com/stretchr/testify/assert"
)
// For now, the tests require the DBHub.io dev docker container be running on its standard
// ports (that means the API server is listening on https://localhost:9444)
func TestMain(m *testing.M) {
log.Println("Seeding the database...")
// Disable https cert validation for our tests
insecureTLS := tls.Config{InsecureSkipVerify: true}
insecureTransport := http.Transport{TLSClientConfig: &insecureTLS}
client := http.Client{Transport: &insecureTransport}
// Seed the database
resp, err := client.Get("https://localhost:9443/x/test/seed")
if err != nil {
log.Fatal(err)
}
if resp.StatusCode != 200 {
log.Fatalf("Database seed request returned http code '%d'. Aborting tests.", resp.StatusCode)
}
log.Println("Database seeding completed ok.")
// Run the tests
log.Println("Running the tests...")
m.Run()
}
// TestBranches verifies retrieving the branch and default branch information using the API
func TestBranches(t *testing.T) {
// Create the local test server connection
conn := serverConnection("Rh3fPl6cl84XEw2FeWtj-FlUsn9OrxKz9oSJfe6kho7jT_1l5hizqw")
// Retrieve branch details for the database
branches, defaultBranch, err := conn.Branches("default", "Assembly Election 2017 with view.sqlite")
if err != nil {
t.Error(err)
return
}
// Verify the returned branch information matches what we're expecting
assert.Len(t, branches, 1)
assert.Contains(t, branches, "main")
assert.Equal(t, "main", defaultBranch)
}
// TestColumns verifies retrieving the list of column names for a database using the API
func TestColumns(t *testing.T) {
// Create the local test server connection
conn := serverConnection("Rh3fPl6cl84XEw2FeWtj-FlUsn9OrxKz9oSJfe6kho7jT_1l5hizqw")
// Retrieve the column info for a table or view in the remote database
table := "Candidate_Names"
columns, err := conn.Columns("default", "Assembly Election 2017 with view.sqlite", Identifier{Branch: "main"}, table)
if err != nil {
log.Fatal(err)
}
// Verify the returned column information matches what we're expecting
assert.Len(t, columns, 2)
assert.Contains(t, columns[0].Name, "Firstname")
assert.Equal(t, "Surname", columns[1].Name)
}
// TestCommits verifies retrieving commit information for standard databases using the API
func TestCommits(t *testing.T) {
// Create the local test server connection
conn := serverConnection("Rh3fPl6cl84XEw2FeWtj-FlUsn9OrxKz9oSJfe6kho7jT_1l5hizqw")
// Retrieve the commit info for a remote database
commits, err := conn.Commits("default", "Assembly Election 2017 with view.sqlite")
if err != nil {
log.Fatal(err)
}
// Verify the returned commit information matches what we're expecting
assert.Len(t, commits, 1)
// Abort early if the returned length of commits isn't what we're expecting
if len(commits) != 1 {
return
}
// Retrieve the the first commit id
var ids []string
for id := range commits {
ids = append(ids, id)
}
firstID := ids[0]
// Verify the commit information is what we're expecting
assert.Equal(t, "default@docker-dev.dbhub.io", commits[firstID].AuthorEmail)
assert.Equal(t, "Default system user", commits[firstID].AuthorName)
assert.Equal(t, "Initial commit", commits[firstID].Message)
assert.Equal(t, "Assembly Election 2017 with view.sqlite", commits[firstID].Tree.Entries[0].Name)
assert.Equal(t, int64(73728), commits[firstID].Tree.Entries[0].Size)
assert.Equal(t, "9cb18719bddb949043abc1ba089dd7c4845ab024ddbe4ad19e9334da4e5b8cdc", commits[firstID].Tree.Entries[0].Sha256)
assert.Equal(t, "9348ddfd44da5a127c59141981954746a860ec8e03e0412cf3af7134af0f97e2", commits[firstID].Tree.Entries[0].LicenceSHA)
}
// TestDatabases verifies retrieving the list of standard databases using the API
func TestDatabases(t *testing.T) {
// Create the local test server connection
conn := serverConnection("Rh3fPl6cl84XEw2FeWtj-FlUsn9OrxKz9oSJfe6kho7jT_1l5hizqw")
// Retrieve the list of databases for the user
databases, err := conn.Databases()
if err != nil {
t.Errorf("Connecting to the API server failed: %v", err)
return
}
// If no databases were found, the test failed
if len(databases) == 0 {
t.Error("No databases found")
return
}
// Verify the expected database names were returned, and only them
assert.Contains(t, databases, "Assembly Election 2017 with view.sqlite")
assert.Contains(t, databases, "Assembly Election 2017.sqlite")
assert.Len(t, databases, 2)
return
}
// TestDatabases verifies retrieving the list of live databases using the API
func TestDatabasesLive(t *testing.T) {
// Create the local test server connection
conn := serverConnection("Rh3fPl6cl84XEw2FeWtj-FlUsn9OrxKz9oSJfe6kho7jT_1l5hizqw")
// Retrieve the list of live databases for the user
databases, err := conn.DatabasesLive()
if err != nil {
t.Error(err)
return
}
// If no databases were found, the test failed
if len(databases) == 0 {
t.Error("No databases found")
return
}
// Verify the expected database name was returned
assert.Contains(t, databases, "Join Testing with index.sqlite")
assert.Len(t, databases, 1)
return
}
// TestDiff verifies the Diff API call
func TestDiff(t *testing.T) {
// Create the local test server connection
conn := serverConnection("Rh3fPl6cl84XEw2FeWtj-FlUsn9OrxKz9oSJfe6kho7jT_1l5hizqw")
// Read the example database file into memory
dbFile := filepath.Join("examples", "upload", "example.db")
z, err := os.ReadFile(dbFile)
if err != nil {
t.Error(err)
return
}
// Upload the example database
dbName := "uploadtest1.sqlite"
err = conn.Upload(dbName, UploadInformation{}, &z)
if err != nil {
t.Error(err)
return
}
t.Cleanup(func() {
// Delete the uploaded database when the test exits
err = conn.Delete(dbName)
if err != nil {
t.Error(err)
return
}
})
// Copy the database file to a temp location so we can make some changes
newFile := filepath.Join(t.TempDir(), "diff-"+randomString(8)+".sqlite")
_, err = fileutils.CopyFile(dbFile, newFile)
if err != nil {
t.Error(err)
return
}
// Make some changes to the copied database file
sdb, err := sqlite.Open(newFile, sqlite.OpenReadWrite|sqlite.OpenFullMutex)
if err != nil {
t.Error()
return
}
dbQuery := `
CREATE TABLE foo (first integer);
INSERT INTO foo (first) values (10);
INSERT INTO foo (first) values (20);`
err = sdb.Exec(dbQuery)
if err != nil {
t.Error()
sdb.Close()
return
}
sdb.Close()
// Retrieve the initial commit id for the database
dbOwner := "default"
commitMap, err := conn.Commits(dbOwner, dbName)
if err != nil {
t.Error(err)
return
}
var commits []string
for idx := range commitMap {
commits = append(commits, idx)
}
firstCommit := commits[0]
// Upload the copied file as a new commit
z, err = os.ReadFile(newFile)
if err != nil {
t.Error(err)
return
}
uploadCommit := Identifier{CommitID: firstCommit}
err = conn.Upload(dbName, UploadInformation{Ident: uploadCommit}, &z)
if err != nil {
t.Error(err)
return
}
// Retrieve the new commit id for the database
commitMap, err = conn.Commits(dbOwner, dbName)
if err != nil {
t.Error(err)
return
}
var secondCommit string
for idx := range commitMap {
if idx != firstCommit {
secondCommit = idx
}
}
// Do the diff using NewPkMerge
commit1 := Identifier{CommitID: firstCommit}
commit2 := Identifier{CommitID: secondCommit}
diffs, err := conn.Diff(dbOwner, dbName, commit1, "", "", commit2, NewPkMerge)
if err != nil {
t.Error(err)
return
}
// Verify the changes
assert.Len(t, diffs.Diff, 1)
assert.Equal(t, "foo", diffs.Diff[0].ObjectName)
assert.Equal(t, "table", diffs.Diff[0].ObjectType)
assert.Equal(t, DiffType("add"), diffs.Diff[0].Schema.ActionType)
assert.Equal(t, "CREATE TABLE foo (first integer);", diffs.Diff[0].Schema.Sql)
assert.Equal(t, "", diffs.Diff[0].Schema.Before)
assert.Equal(t, "CREATE TABLE foo (first integer)", diffs.Diff[0].Schema.After)
assert.Equal(t, `INSERT INTO "foo"("first") VALUES(10);`, diffs.Diff[0].Data[0].Sql)
assert.Equal(t, `INSERT INTO "foo"("first") VALUES(20);`, diffs.Diff[0].Data[1].Sql)
// Diff with PreservePkMerge
diffs, err = conn.Diff(dbOwner, dbName, commit1, "", "", commit2, PreservePkMerge)
if err != nil {
t.Error(err)
return
}
// Verify the changes
assert.Len(t, diffs.Diff, 1)
assert.Equal(t, "foo", diffs.Diff[0].ObjectName)
assert.Equal(t, "table", diffs.Diff[0].ObjectType)
assert.Equal(t, DiffType("add"), diffs.Diff[0].Schema.ActionType)
assert.Equal(t, "CREATE TABLE foo (first integer);", diffs.Diff[0].Schema.Sql)
assert.Equal(t, "", diffs.Diff[0].Schema.Before)
assert.Equal(t, "CREATE TABLE foo (first integer)", diffs.Diff[0].Schema.After)
assert.Equal(t, `INSERT INTO "foo"("first") VALUES(10);`, diffs.Diff[0].Data[0].Sql)
assert.Equal(t, `INSERT INTO "foo"("first") VALUES(20);`, diffs.Diff[0].Data[1].Sql)
// Diff with NoMerge
diffs, err = conn.Diff(dbOwner, dbName, commit1, "", "", commit2, NoMerge)
if err != nil {
t.Error(err)
return
}
// Verify the changes
assert.Len(t, diffs.Diff, 1)
assert.Equal(t, "foo", diffs.Diff[0].ObjectName)
assert.Equal(t, "table", diffs.Diff[0].ObjectType)
assert.Equal(t, DiffType("add"), diffs.Diff[0].Schema.ActionType)
assert.Equal(t, "", diffs.Diff[0].Schema.Sql)
assert.Equal(t, "", diffs.Diff[0].Schema.Before)
assert.Equal(t, "CREATE TABLE foo (first integer)", diffs.Diff[0].Schema.After)
assert.Equal(t, "", diffs.Diff[0].Data[0].Sql)
assert.Equal(t, "", diffs.Diff[0].Data[1].Sql)
}
// TestExecute verifies the Execute API call
func TestExecute(t *testing.T) {
// Create the local test server connection
conn := serverConnection("Rh3fPl6cl84XEw2FeWtj-FlUsn9OrxKz9oSJfe6kho7jT_1l5hizqw")
// Execute a SQL statement
dbQuery := `INSERT INTO table1 (id, Name) VALUES (7, "Stuff")`
rowsChanged, err := conn.Execute("default", "Join Testing with index.sqlite", dbQuery)
if err != nil {
t.Error(err)
return
}
// Verify the result
assert.Equal(t, 1, rowsChanged)
// Execute another SQL statement
dbQuery = `UPDATE table1 SET Name = "New Stuff" WHERE id = 1 OR id = 7`
rowsChanged, err = conn.Execute("default", "Join Testing with index.sqlite", dbQuery)
if err != nil {
t.Error(err)
return
}
// Verify the result
assert.Equal(t, 2, rowsChanged)
}
// TestIndexes verifies the Indexes API call
func TestIndexes(t *testing.T) {
// Create the local test server connection
conn := serverConnection("Rh3fPl6cl84XEw2FeWtj-FlUsn9OrxKz9oSJfe6kho7jT_1l5hizqw")
// Retrieve the index information for the database
indexes, err := conn.Indexes("default", "Join Testing with index.sqlite", Identifier{})
if err != nil {
t.Error(err)
return
}
// Verify the index information
assert.Len(t, indexes, 1)
assert.Equal(t, "table1", indexes[0].Table)
assert.Equal(t, "stuff", indexes[0].Name)
assert.Empty(t, indexes[0].Columns[0].CID)
assert.Equal(t, "id", indexes[0].Columns[0].Name)
}
// TestMetadata verifies the metadata API call
func TestMetadata(t *testing.T) {
// Create the local test server connection
conn := serverConnection("Rh3fPl6cl84XEw2FeWtj-FlUsn9OrxKz9oSJfe6kho7jT_1l5hizqw")
// Retrieve the metadata information for the database
meta, err := conn.Metadata("default", "Assembly Election 2017 with view.sqlite")
if err != nil {
t.Error(err)
return
}
// Get the commit id of the first commit
var firstCommit string
for _, c := range meta.Commits {
firstCommit = c.ID
}
// Verify the metadata info
assert.Equal(t, "https://docker-dev.dbhub.io:9443/default/Assembly Election 2017 with view.sqlite", meta.WebPage)
assert.Equal(t, "main", meta.DefBranch)
assert.Equal(t, "", meta.Branches["main"].Description)
assert.Equal(t, 1, meta.Branches["main"].CommitCount)
assert.Empty(t, meta.Releases)
assert.Empty(t, meta.Tags)
assert.Equal(t, firstCommit, meta.Commits[firstCommit].ID)
assert.Equal(t, "Initial commit", meta.Commits[firstCommit].Message)
assert.Equal(t, "Default system user", meta.Commits[firstCommit].AuthorName)
assert.Equal(t, "default@docker-dev.dbhub.io", meta.Commits[firstCommit].AuthorEmail)
assert.Equal(t, int64(73728), meta.Commits[firstCommit].Tree.Entries[0].Size)
}
// TestQuery verifies the Query API call
func TestQuery(t *testing.T) {
// Create the local test server connection
conn := serverConnection("Rh3fPl6cl84XEw2FeWtj-FlUsn9OrxKz9oSJfe6kho7jT_1l5hizqw")
// Query the database
dbQuery := `
SELECT id, Name
FROM table1
ORDER BY Name DESC`
result, err := conn.Query("default", "Join Testing with index.sqlite", Identifier{}, false, dbQuery)
if err != nil {
t.Error(err)
return
}
// Verify the result
assert.Len(t, result.Rows, 7)
assert.Contains(t, result.Rows, ResultRow{Fields: []string{"2", "Bar"}})
assert.Contains(t, result.Rows, ResultRow{Fields: []string{"3", "Baz"}})
assert.Contains(t, result.Rows, ResultRow{Fields: []string{"4", "Blumph"}})
assert.Contains(t, result.Rows, ResultRow{Fields: []string{"5", "Blargo"}})
assert.Contains(t, result.Rows, ResultRow{Fields: []string{"6", "Batty"}})
}
// TestReleases verifies the Releases API call
func TestReleases(t *testing.T) {
// Create the local test server connection
conn := serverConnection("Rh3fPl6cl84XEw2FeWtj-FlUsn9OrxKz9oSJfe6kho7jT_1l5hizqw")
// Retrieve the releases for a database
releases, err := conn.Releases("default", "Assembly Election 2017.sqlite")
if err != nil {
t.Error(err)
return
}
// Verify the retrieved information
assert.Len(t, releases, 2)
assert.Equal(t, "First release", releases["first"].Description)
assert.Equal(t, "Example Releaser", releases["first"].ReleaserName)
assert.Equal(t, "example@example.org", releases["first"].ReleaserEmail)
assert.Equal(t, "Second release", releases["second"].Description)
assert.Equal(t, "Example Releaser", releases["second"].ReleaserName)
assert.Equal(t, "example@example.org", releases["second"].ReleaserEmail)
}
// TestTables verifies the Tables API call
func TestTables(t *testing.T) {
// Create the local test server connection
conn := serverConnection("Rh3fPl6cl84XEw2FeWtj-FlUsn9OrxKz9oSJfe6kho7jT_1l5hizqw")
// Retrieve table information for a database
tbls, err := conn.Tables("default", "Assembly Election 2017.sqlite", Identifier{})
if err != nil {
t.Error(err)
return
}
// Verify the returned information
assert.Len(t, tbls, 3)
assert.Contains(t, tbls, "Candidate_Information")
assert.Contains(t, tbls, "Constituency_Turnout_Information")
assert.Contains(t, tbls, "Elected_Candidates")
}
// TestTags verifies the Tags API call
func TestTags(t *testing.T) {
// Create the local test server connection
conn := serverConnection("Rh3fPl6cl84XEw2FeWtj-FlUsn9OrxKz9oSJfe6kho7jT_1l5hizqw")
// Retrieve the tags for a database
tags, err := conn.Tags("default", "Assembly Election 2017.sqlite")
if err != nil {
t.Error(err)
return
}
// Verify the retrieved information
assert.Len(t, tags, 2)
assert.Equal(t, "First tag", tags["first"].Description)
assert.Equal(t, "Example Tagger", tags["first"].TaggerName)
assert.Equal(t, "example@example.org", tags["first"].TaggerEmail)
assert.Equal(t, "Second tag", tags["second"].Description)
assert.Equal(t, "Example Tagger", tags["second"].TaggerName)
assert.Equal(t, "example@example.org", tags["second"].TaggerEmail)
}
// TestUpload verifies uploading a standard database via the API
func TestUpload(t *testing.T) {
// Create the local test server connection
conn := serverConnection("Rh3fPl6cl84XEw2FeWtj-FlUsn9OrxKz9oSJfe6kho7jT_1l5hizqw")
// Read the example database file into memory
dbFile := filepath.Join("examples", "upload", "example.db")
z, err := os.ReadFile(dbFile)
if err != nil {
t.Error(err)
return
}
// Upload the example database
dbName := "testupload.sqlite"
err = conn.Upload(dbName, UploadInformation{}, &z)
if err != nil {
t.Error(err)
return
}
// Verify the file contents. This is done by downloading the database and doing a byte comparison to ensure its
// identical to the upload
downloaded, err := conn.Download("default", dbName, Identifier{})
if err != nil {
t.Error(err)
return
}
t.Cleanup(func() {
// Delete the uploaded file when the function exits
err = conn.Delete(dbName)
if err != nil {
t.Error(err)
return
}
})
data, err := io.ReadAll(downloaded)
if err != nil {
t.Error(err)
return
}
result := bytes.Compare(z, data)
if result != 0 {
t.Errorf("Standard database upload succeeded, but failed verification when downloading it and comparing to the original")
return
}
}
// TestUploadLive verifies uploading a live database via the API
func TestUploadLive(t *testing.T) {
// Create the local test server connection
conn := serverConnection("Rh3fPl6cl84XEw2FeWtj-FlUsn9OrxKz9oSJfe6kho7jT_1l5hizqw")
// Read the example database file into memory
dbA := filepath.Join("examples", "upload", "example.db")
z, err := os.ReadFile(dbA)
if err != nil {
t.Error(err)
return
}
// Upload the database
dbName := "testuploadlive.sqlite"
err = conn.UploadLive(dbName, &z)
if err != nil {
t.Error(err)
return
}
// *** Verify the database ***
// This is done by downloading and comparing (database diff) the database contents with the database file that was uploaded
downloaded, err := conn.Download("default", dbName, Identifier{})
if err != nil {
t.Error(err)
return
}
t.Cleanup(func() {
// Delete the uploaded database when the test exits
err = conn.Delete(dbName)
if err != nil {
t.Error(err)
return
}
})
data, err := io.ReadAll(downloaded)
if err != nil {
t.Error(err)
return
}
dbB := filepath.Join(t.TempDir(), "diff-"+randomString(8)+".sqlite")
err = os.WriteFile(dbB, data, 0750)
if err != nil {
t.Error(err)
return
}
// Do the comparison
diffs, err := common.DBDiff(dbA, dbB, common.NoMerge, false)
if err != nil {
t.Error(err)
return
}
assert.Empty(t, diffs.Diff)
}
// TestViews verifies the Views API call
func TestViews(t *testing.T) {
// Create the local test server connection
conn := serverConnection("Rh3fPl6cl84XEw2FeWtj-FlUsn9OrxKz9oSJfe6kho7jT_1l5hizqw")
// Get the list of views in the database
views, err := conn.Views("default", "Assembly Election 2017 with view.sqlite", Identifier{})
if err != nil {
t.Error(err)
return
}
assert.Len(t, views, 1)
assert.Equal(t, "Candidate_Names", views[0])
}
// TestWebpage verifies the Webpage API call
func TestWebpage(t *testing.T) {
// Create the local test server connection
conn := serverConnection("Rh3fPl6cl84XEw2FeWtj-FlUsn9OrxKz9oSJfe6kho7jT_1l5hizqw")
// Gather the data then test the result
pageData, err := conn.Webpage("default", "Assembly Election 2017.sqlite")
if err != nil {
t.Error(err)
return
}
assert.Equal(t, "https://docker-dev.dbhub.io:9443/default/Assembly Election 2017.sqlite", pageData.WebPage)
}
// randomString generates a random alphanumeric string of the desired length
func randomString(length int) string {
rand.Seed(time.Now().UnixNano())
const alphaNum = "abcdefghijklmnopqrstuvwxyz0123456789"
randomString := make([]byte, length)
for i := range randomString {
randomString[i] = alphaNum[rand.Intn(len(alphaNum))]
}
return string(randomString)
}
// serverConnection is a utility function that sets up the API connection object to the test server, ready for use
func serverConnection(apiKey string) Connection {
// Create a new DBHub.io API object
db, err := New(apiKey)
if err != nil {
log.Fatal(err)
}
db.ChangeServer("https://localhost:9444")
db.ChangeVerifyServerCert(false)
return db
}