Skip to content

Commit

Permalink
Migrate tests to reflect new PostgresDB design
Browse files Browse the repository at this point in the history
Very little of the tests themselves had to be rewritten to reflect
the system being postgres, however a number of tests that relied on
mocking Mongo are no longer valid in the Postgres implementation
and have been removed.

We now spin up a test PSQL database container using an initialiser
sql file to test against.
  • Loading branch information
Tetrino committed Sep 6, 2023
1 parent d538026 commit 16aeef6
Show file tree
Hide file tree
Showing 5 changed files with 144 additions and 190 deletions.
30 changes: 25 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,34 @@ lint:
unit_tests:
go test -race $$(go list ./... | grep -v integration_tests)

integration_tests: build start_mongo
integration_tests: build start_postgres init_test_db
go test -race -v ./integration_tests

start_mongo:
./mongo.sh start
start_postgres:
@if [ -z $$(docker ps -aqf name=router-postgres-test-db) ]; then \
docker run \
--name router-postgres-test-db \
-e POSTGRES_HOST_AUTH_METHOD=trust \
-d \
-p 5432:5432 \
--user 'postgres' \
--health-cmd 'pg_isready' \
--health-start-period 5s \
postgres:14; \
echo Waiting for postgres to be up; \
until [ "`docker inspect -f '{{.State.Health.Status}}' router-postgres-test-db`" = "healthy" ]; do \
echo '.\c'; \
sleep 1; \
done; \
else \
echo "PostgreSQL container 'router-postgres-test-db' already exists. Skipping creation."; \
fi

stop_mongo:
./mongo.sh stop
init_test_db:
docker exec -i router-postgres-test-db psql < localdb_init.sql

cleanup_postgres:
@docker rm -f router-postgres-test-db || true

update_deps:
go get -t -u ./... && go mod tidy && go mod vendor
73 changes: 49 additions & 24 deletions integration_tests/route_helpers.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package integration

import (
"database/sql"
"fmt"
"os"
"time"

"github.com/globalsign/mgo"
"github.com/globalsign/mgo/bson"
_ "github.com/lib/pq" // Without which we can't use PSQL calls

// revive:disable:dot-imports
. "github.com/onsi/ginkgo/v2"
Expand All @@ -19,18 +19,18 @@ var _ = AfterEach(func() {
})

var (
routerDB *mgo.Database
routerDB *sql.DB
)

type Route struct {
IncomingPath string `bson:"incoming_path"`
RouteType string `bson:"route_type"`
Handler string `bson:"handler"`
BackendID string `bson:"backend_id"`
RedirectTo string `bson:"redirect_to"`
RedirectType string `bson:"redirect_type"`
SegmentsMode string `bson:"segments_mode"`
Disabled bool `bson:"disabled"`
IncomingPath string
RouteType string
Handler string
BackendID string
RedirectTo string
RedirectType string
SegmentsMode string
Disabled bool
}

func NewBackendRoute(backendID string, extraParams ...string) Route {
Expand Down Expand Up @@ -80,36 +80,61 @@ func NewGoneRoute(extraParams ...string) Route {
}

func initRouteHelper() error {
databaseURL := os.Getenv("ROUTER_MONGO_URL")
databaseURL := os.Getenv("DATABASE_URL")

if databaseURL == "" {
databaseURL = "127.0.0.1"
databaseURL = "postgresql://postgres@127.0.0.1:5432/router_test?sslmode=disable"
}

sess, err := mgo.Dial(databaseURL)
db, err := sql.Open("postgres", databaseURL)
if err != nil {
return fmt.Errorf("failed to connect to mongo: %w", err)
return fmt.Errorf("Failed to connect to Postgres: " + err.Error())
}
sess.SetSyncTimeout(10 * time.Minute)
sess.SetSocketTimeout(10 * time.Minute)

routerDB = sess.DB("router_test")
db.SetConnMaxLifetime(10 * time.Minute)
db.SetMaxIdleConns(0)
db.SetMaxOpenConns(10)

routerDB = db
return nil
}

func addBackend(id, url string) {
err := routerDB.C("backends").Insert(bson.M{"backend_id": id, "backend_url": url})
Expect(err).NotTo(HaveOccurred())
query := `
INSERT INTO backends (backend_id, backend_url, created_at, updated_at)
VALUES ($1, $2, $3, $4)
`

_, err := routerDB.Exec(query, id, url, time.Now(), time.Now())
Expect(err).ToNot(HaveOccurred())
}

func addRoute(path string, route Route) {
route.IncomingPath = path

err := routerDB.C("routes").Insert(route)
Expect(err).NotTo(HaveOccurred())
query := `
INSERT INTO routes (incoming_path, route_type, handler, backend_id, redirect_to, redirect_type, segments_mode, disabled, created_at, updated_at)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
`

_, err := routerDB.Exec(
query,
route.IncomingPath,
route.RouteType,
route.Handler,
route.BackendID,
route.RedirectTo,
route.RedirectType,
route.SegmentsMode,
route.Disabled,
time.Now(),
time.Now(),
)

Expect(err).ToNot(HaveOccurred())
}

func clearRoutes() {
_ = routerDB.C("routes").DropCollection()
_ = routerDB.C("backends").DropCollection()
_, err := routerDB.Exec("DELETE FROM routes; DELETE FROM backends")
Expect(err).ToNot(HaveOccurred())
}
6 changes: 3 additions & 3 deletions integration_tests/router_support.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ import (
)

const (
routerPort = 3169
apiPort = 3168
routerPort = 5434
apiPort = 5433
)

func routerURL(port int, path string) string {
Expand Down Expand Up @@ -56,7 +56,7 @@ func startRouter(port, apiPort int, extraEnv []string) error {
}
cmd := exec.Command(bin)

cmd.Env = append(cmd.Environ(), "ROUTER_MONGO_DB=router_test")
cmd.Env = append(cmd.Environ(), "DATABASE_NAME=router_test")
cmd.Env = append(cmd.Env, fmt.Sprintf("ROUTER_PUBADDR=%s", pubAddr))
cmd.Env = append(cmd.Env, fmt.Sprintf("ROUTER_APIADDR=%s", apiAddr))
cmd.Env = append(cmd.Env, fmt.Sprintf("ROUTER_ERROR_LOG=%s", tempLogfile.Name()))
Expand Down
158 changes: 0 additions & 158 deletions lib/router_test.go
Original file line number Diff line number Diff line change
@@ -1,171 +1,13 @@
package router

import (
"errors"
"testing"
"time"

"github.com/globalsign/mgo/bson"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)

type mockMongoDB struct {
result bson.M
err error
}

func (m *mockMongoDB) Run(_ interface{}, res interface{}) error {
if m.err != nil {
return m.err
}

bytes, err := bson.Marshal(m.result)
if err != nil {
return err
}

err = bson.Unmarshal(bytes, res)
if err != nil {
return err
}

return nil
}

func TestRouter(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Router Suite")
}

var _ = Describe("Router", func() {
Context("When calling shouldReload", func() {
Context("with an up-to-date mongo instance", func() {
It("should return false", func() {
rt := Router{}
initialOptime, _ := bson.NewMongoTimestamp(time.Date(2021, time.March, 12, 8, 0, 0, 0, time.UTC), 1)
rt.mongoReadToOptime = initialOptime

currentOptime, _ := bson.NewMongoTimestamp(time.Date(2021, time.March, 12, 8, 0, 0, 0, time.UTC), 1)
mongoInstance := MongoReplicaSetMember{}
mongoInstance.Optime = currentOptime

Expect(rt.shouldReload(mongoInstance)).To(
BeFalse(),
"Router should determine no reload is necessary when Mongo optime hasn't changed",
)
})
})

Context("with a stale mongo instance", func() {
It("should return false when timestamp differs", func() {
rt := Router{}
initialOptime, _ := bson.NewMongoTimestamp(time.Date(2021, time.March, 12, 8, 0, 0, 0, time.UTC), 1)
rt.mongoReadToOptime = initialOptime

currentOptime, _ := bson.NewMongoTimestamp(time.Date(2021, time.March, 12, 8, 2, 30, 0, time.UTC), 1)
mongoInstance := MongoReplicaSetMember{}
mongoInstance.Optime = currentOptime

Expect(rt.shouldReload(mongoInstance)).To(
BeTrue(),
"Router should determine reload is necessary when Mongo optime has changed by timestamp",
)
})

It("should return false when operand differs", func() {
rt := Router{}
initialOptime, _ := bson.NewMongoTimestamp(time.Date(2021, time.March, 12, 8, 0, 0, 0, time.UTC), 1)
rt.mongoReadToOptime = initialOptime

currentOptime, _ := bson.NewMongoTimestamp(time.Date(2021, time.March, 12, 8, 0, 0, 0, time.UTC), 2)
mongoInstance := MongoReplicaSetMember{}
mongoInstance.Optime = currentOptime

Expect(rt.shouldReload(mongoInstance)).To(
BeTrue(),
"Router should determine reload is necessary when Mongo optime has changed by operand",
)
})
})
})

Context("When calling getCurrentMongoInstance", func() {
It("should return error when unable to get the replica set", func() {
mockMongoObj := &mockMongoDB{
err: errors.New("Error connecting to replica set"),
}

rt := Router{}
_, err := rt.getCurrentMongoInstance(mockMongoObj)

Expect(err).To(
HaveOccurred(),
"Router should raise an error when it can't get replica set status from Mongo")
})

It("should return fail to find an instance when the replica set status schema doesn't match the expected schema", func() {
replicaSetStatusBson := bson.M{"members": []bson.M{{"unknownProperty": "unknown"}}}
mockMongoObj := &mockMongoDB{
result: replicaSetStatusBson,
}

rt := Router{}
_, err := rt.getCurrentMongoInstance(mockMongoObj)

Expect(err).To(
HaveOccurred(),
"Router should raise an error when the current Mongo instance can't be found in the replica set status response")
})

It("should return fail to find an instance when the replica set status contains no instances marked with self:true", func() {
replicaSetStatusBson := bson.M{"members": []bson.M{{"name": "mongo1", "self": false}}}
mockMongoObj := &mockMongoDB{
result: replicaSetStatusBson,
}

rt := Router{}
_, err := rt.getCurrentMongoInstance(mockMongoObj)

Expect(err).To(
HaveOccurred(),
"Router should raise an error when the current Mongo instance can't be found in the replica set status response")
})

It("should return fail to find an instance when the replica set status contains multiple instances marked with self:true", func() {
replicaSetStatusBson := bson.M{"members": []bson.M{{"name": "mongo1", "self": true}, {"name": "mongo2", "self": true}}}
mockMongoObj := &mockMongoDB{
result: replicaSetStatusBson,
}

rt := Router{}
_, err := rt.getCurrentMongoInstance(mockMongoObj)

Expect(err).To(
HaveOccurred(),
"Router should raise an error when the replica set status response contains multiple current Mongo instances")
})

It("should successfully return the current Mongo instance from the replica set", func() {
replicaSetStatusBson := bson.M{"members": []bson.M{{"name": "mongo1", "self": false}, {"name": "mongo2", "optime": 6945383634312364034, "self": true}}}
mockMongoObj := &mockMongoDB{
result: replicaSetStatusBson,
}

expectedMongoInstance := MongoReplicaSetMember{
Name: "mongo2",
Optime: 6945383634312364034,
Current: true,
}

rt := Router{}
currentMongoInstance, _ := rt.getCurrentMongoInstance(mockMongoObj)

Expect(currentMongoInstance).To(
Equal(expectedMongoInstance),
"Router should get the current Mongo instance from the replica set status response",
)
})
})
})
Loading

0 comments on commit 16aeef6

Please sign in to comment.