Skip to content

Commit

Permalink
System Tests with full in memory DB support (#57)
Browse files Browse the repository at this point in the history
This PR implements full system tests / end2end tests using an in memory
mysql databse. The motivation for this work was to get coverage for the
lines that require a real db connection.

These tests are still expensive to run, as the internal DB server needs
to be started (~50ms), so only need to have system tests for fully
testing a feature. Leave smaller tests for unit tests.
  • Loading branch information
matheusgomes28 authored Mar 19, 2024
1 parent 4f0c2c6 commit 1672494
Show file tree
Hide file tree
Showing 8 changed files with 774 additions and 18 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/failfast.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ jobs:

steps:
- uses: actions/checkout@v3

- name: Prepare Environment
run: make prepare_env

- uses: ./.github/workflows/actions/golangci-lint
name: Running Linters 🧪
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ jobs:

- name: Running Go Tests 🧪
run: |
make prepare_env
go test -coverprofile=coverage.out -covermode=atomic ./... -v
- name: Upload coverage to Codecov 📓
Expand Down
7 changes: 5 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,15 @@ ADMIN_BINARY_NAME=urchin-admin

all: build test

build:
prepare_env:
cp -r migrations tests/system_tests/endpoint_tests/

build: prepare_env
$(TEMPL) generate
GIN_MODE=release $(GOCMD) build -ldflags "-s" -v -o $(BUILD_DIR)/$(BINARY_NAME) $(URCHIN_DIR)
GIN_MODE=release $(GOCMD) build -ldflags "-s" -v -o $(BUILD_DIR)/$(ADMIN_BINARY_NAME) $(URCHIN_ADMIN_DIR)

test:
test: prepare_env
$(GOCMD) test -v ./...

clean:
Expand Down
39 changes: 34 additions & 5 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,41 +5,70 @@ go 1.22.0
require (
github.com/BurntSushi/toml v1.3.2
github.com/a-h/templ v0.2.543
github.com/dolthub/go-mysql-server v0.18.1-0.20240317073429-152477c4b580
github.com/fossoreslp/go-uuid-v4 v1.0.0
github.com/gin-gonic/gin v1.9.1
github.com/go-sql-driver/mysql v1.7.1
github.com/go-sql-driver/mysql v1.8.0
github.com/gomarkdown/markdown v0.0.0-20231222211730-1d6d20845b47
github.com/pelletier/go-toml/v2 v2.0.8
github.com/pressly/goose/v3 v3.19.2
github.com/rs/zerolog v1.31.0
github.com/stretchr/testify v1.8.4
github.com/zutto/shardedmap v0.0.0-20180201164343-415202d0910e
)

require (
filippo.io/edwards25519 v1.1.0 // indirect
github.com/bytedance/sonic v1.9.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dolthub/flatbuffers/v23 v23.3.3-dh.2 // indirect
github.com/dolthub/go-icu-regex v0.0.0-20230524105445-af7e7991c97e // indirect
github.com/dolthub/jsonpath v0.0.2-0.20240227200619-19675ab05c71 // indirect
github.com/dolthub/vitess v0.0.0-20240312232959-8ee510931c7b // indirect
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-kit/kit v0.10.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.14.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/hashicorp/golang-lru v0.5.4 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
github.com/leodido/go-urn v1.2.4 // indirect
github.com/lestrrat-go/strftime v1.0.4 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mfridman/interpolate v0.0.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/sethvargo/go-retry v0.2.4 // indirect
github.com/shopspring/decimal v1.3.1 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/tetratelabs/wazero v1.1.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.11 // indirect
go.opentelemetry.io/otel v1.20.0 // indirect
go.opentelemetry.io/otel/trace v1.20.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/arch v0.3.0 // indirect
golang.org/x/crypto v0.14.0 // indirect
golang.org/x/net v0.17.0 // indirect
golang.org/x/crypto v0.18.0 // indirect
golang.org/x/exp v0.0.0-20231108232855-2478ac86f678 // indirect
golang.org/x/mod v0.14.0 // indirect
golang.org/x/net v0.20.0 // indirect
golang.org/x/sync v0.6.0 // indirect
golang.org/x/sys v0.16.0 // indirect
golang.org/x/text v0.13.0 // indirect
google.golang.org/protobuf v1.30.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/tools v0.17.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17 // indirect
google.golang.org/grpc v1.59.0 // indirect
google.golang.org/protobuf v1.31.0 // indirect
gopkg.in/src-d/go-errors.v1 v1.0.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
548 changes: 537 additions & 11 deletions go.sum

Large diffs are not rendered by default.

90 changes: 90 additions & 0 deletions tests/system_tests/endpoint_tests/helpers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package index_test

import (
"context"
_ "database/sql"
"embed"
"fmt"
"time"

_ "github.com/go-sql-driver/mysql"

sqle "github.com/dolthub/go-mysql-server"
"github.com/dolthub/go-mysql-server/memory"
"github.com/dolthub/go-mysql-server/server"
"github.com/dolthub/go-mysql-server/sql"
"github.com/matheusgomes28/urchin/common"
"github.com/matheusgomes28/urchin/database"
)

//go:generate ../../../migrations ./migrations

//go:embed migrations/*.sql
var embedMigrations embed.FS

var current_port int = 0

func runDatabaseServer(app_settings common.AppSettings) {
pro := createTestDatabase(app_settings.DatabaseName)
engine := sqle.NewDefault(pro)
engine.Analyzer.Catalog.MySQLDb.AddRootAccount()

session := memory.NewSession(sql.NewBaseSession(), pro)
ctx := sql.NewContext(context.Background(), sql.WithSession(session))
ctx.SetCurrentDatabase(app_settings.DatabaseName)

config := server.Config{
Protocol: "tcp",
Address: fmt.Sprintf("%s:%d", app_settings.DatabaseAddress, app_settings.DatabasePort),
}
s, err := server.NewServer(config, engine, memory.NewSessionBuilder(pro), nil)
if err != nil {
panic(err)
}
if err = s.Start(); err != nil {
panic(err)
}
}

func createTestDatabase(name string) *memory.DbProvider {
db := memory.NewDatabase(name)
db.BaseDatabase.EnablePrimaryKeyIndexes()

pro := memory.NewDBProvider(db)
return pro
}

func waitForDb(app_settings common.AppSettings) (database.SqlDatabase, error) {

for range 400 {
database, err := database.MakeSqlConnection(
app_settings.DatabaseUser,
app_settings.DatabasePassword,
app_settings.DatabaseAddress,
app_settings.DatabasePort,
app_settings.DatabaseName,
)

if err == nil {
return database, nil
}

time.Sleep(25 * time.Millisecond)
}

return database.SqlDatabase{}, fmt.Errorf("database did not start")
}

func getAppSettings() common.AppSettings {
app_settings := common.AppSettings{
DatabaseAddress: "localhost",
DatabasePort: 3336 + current_port, // Initial port
DatabaseUser: "root",
DatabasePassword: "",
DatabaseName: "urchin",
WebserverPort: 8080,
}
current_port++

return app_settings
}
65 changes: 65 additions & 0 deletions tests/system_tests/endpoint_tests/index_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package index_test

import (
_ "database/sql"
"net/http"
"net/http/httptest"
"testing"

_ "github.com/go-sql-driver/mysql"
"github.com/stretchr/testify/require"

"github.com/matheusgomes28/urchin/app"
"github.com/pressly/goose/v3"
)

func TestIndexPagePing(t *testing.T) {

// This is gonna be the in-memory mysql
app_settings := getAppSettings()
go runDatabaseServer(app_settings)
database, err := waitForDb(app_settings)
require.Nil(t, err)
goose.SetBaseFS(embedMigrations)

if err := goose.SetDialect("mysql"); err != nil {
require.Nil(t, err)
}

if err := goose.Up(database.Connection, "migrations"); err != nil {
require.Nil(t, err)
}

r := app.SetupRoutes(app_settings, database)
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/", nil)
r.ServeHTTP(w, req)

require.Equal(t, http.StatusOK, w.Code)
}

func TestIndexPagePostExists(t *testing.T) {
// This is gonna be the in-memory mysql
app_settings := getAppSettings()
go runDatabaseServer(app_settings)
database, err := waitForDb(app_settings)
require.Nil(t, err)
goose.SetBaseFS(embedMigrations)

if err := goose.SetDialect("mysql"); err != nil {
require.Nil(t, err)
}

if err := goose.Up(database.Connection, "migrations"); err != nil {
require.Nil(t, err)
}

r := app.SetupRoutes(app_settings, database)
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/", nil)
r.ServeHTTP(w, req)

require.Equal(t, http.StatusOK, w.Code)

require.Contains(t, w.Body.String(), "My Very First Post")
}
39 changes: 39 additions & 0 deletions tests/system_tests/endpoint_tests/post_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package index_test

import (
_ "database/sql"
"net/http"
"net/http/httptest"
"testing"

_ "github.com/go-sql-driver/mysql"
"github.com/stretchr/testify/require"

"github.com/matheusgomes28/urchin/app"
"github.com/pressly/goose/v3"
)

func TestPostExists(t *testing.T) {

// This is gonna be the in-memory mysql
app_settings := getAppSettings()
go runDatabaseServer(app_settings)
database, err := waitForDb(app_settings)
require.Nil(t, err)
goose.SetBaseFS(embedMigrations)

if err := goose.SetDialect("mysql"); err != nil {
require.Nil(t, err)
}

if err := goose.Up(database.Connection, "migrations"); err != nil {
require.Nil(t, err)
}

r := app.SetupRoutes(app_settings, database)
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/post/1", nil)
r.ServeHTTP(w, req)

require.Equal(t, http.StatusOK, w.Code)
}

0 comments on commit 1672494

Please sign in to comment.