From 8c10e0be023b80ce913b3dced2e34ff5850fa3ac Mon Sep 17 00:00:00 2001 From: Kyle Conroy Date: Thu, 27 Jun 2019 13:06:08 -0700 Subject: [PATCH 1/5] Add tests that hit a real database --- parser.go | 13 +- parser_test.go | 6 +- testdata/ondeck/db.go | 24 +- testdata/ondeck/db_test.go | 284 +++++++++++++++++++++ testdata/ondeck/go.mod | 8 + testdata/ondeck/go.sum | 4 + testdata/ondeck/{ => prepared}/prepared.go | 30 +-- testdata/ondeck/query/venue.sql | 10 +- 8 files changed, 327 insertions(+), 52 deletions(-) create mode 100644 testdata/ondeck/db_test.go create mode 100644 testdata/ondeck/go.mod create mode 100644 testdata/ondeck/go.sum rename testdata/ondeck/{ => prepared}/prepared.go (94%) diff --git a/parser.go b/parser.go index bd16f68057..f7658c6f24 100644 --- a/parser.go +++ b/parser.go @@ -246,6 +246,9 @@ func ParseQueries(s *postgres.Schema, dir string) (*Result, error) { } var q []Query for _, f := range files { + if !strings.HasSuffix(f.Name(), ".sql") { + continue + } blob, err := ioutil.ReadFile(filepath.Join(dir, f.Name())) if err != nil { return nil, err @@ -609,22 +612,12 @@ var hh = `package {{.Package}} import ( "context" "database/sql" - {{with .Schema.Enums}}"database/sql/driver"{{end}} {{if .ImportTime}}"time"{{end}} ) {{range .Schema.Enums}} type {{.GoName}} string -func (e *{{.GoName}}) Scan(v interface{}) error { - *e = {{.GoName}}(string(v.([]byte))) - return nil -} - -func (e {{.GoName}}) Value() (driver.Value, error) { - return []byte(string(e)), nil -} - const ( {{- range $i, $c := .Constants}} {{- if eq $i 0}} diff --git a/parser_test.go b/parser_test.go index 60ae94d928..064da5fbf5 100644 --- a/parser_test.go +++ b/parser_test.go @@ -102,9 +102,9 @@ func TestParseSchema(t *testing.T) { }) t.Run("prepared", func(t *testing.T) { - source := generate(q, "ondeck", true) + source := generate(q, "prepared", true) - blob, err := ioutil.ReadFile(filepath.Join("testdata", "ondeck", "prepared.go")) + blob, err := ioutil.ReadFile(filepath.Join("testdata", "ondeck", "prepared", "prepared.go")) if err != nil { log.Fatal(err) } @@ -119,7 +119,7 @@ func TestParseSchema(t *testing.T) { func TestCompile(t *testing.T) { files := []string{ filepath.Join("testdata", "ondeck", "db.go"), - filepath.Join("testdata", "ondeck", "prepared.go"), + filepath.Join("testdata", "ondeck", "prepared", "prepared.go"), } for _, filename := range files { f := filename diff --git a/testdata/ondeck/db.go b/testdata/ondeck/db.go index 3ec8d8db67..6d2340bf13 100644 --- a/testdata/ondeck/db.go +++ b/testdata/ondeck/db.go @@ -3,21 +3,11 @@ package ondeck import ( "context" "database/sql" - "database/sql/driver" "time" ) type StatusEnum string -func (e *StatusEnum) Scan(v interface{}) error { - *e = StatusEnum(string(v.([]byte))) - return nil -} - -func (e StatusEnum) Value() (driver.Value, error) { - return []byte(string(e)), nil -} - const ( StatusOpen StatusEnum = "open" StatusClosed = "closed" @@ -79,22 +69,24 @@ func (q *Queries) CreateCity(ctx context.Context, name string, slug string) (Cit const createVenue = `-- name: CreateVenue :one INSERT INTO venue ( - name, slug, + name, + city, created_at, spotify_playlist, - city + status ) VALUES ( $1, $2, - NOW(), $3, - $4 + NOW(), + $4, + $5 ) RETURNING id ` -func (q *Queries) CreateVenue(ctx context.Context, name string, slug string, spotifyPlaylist string, city string) (int, error) { - row := q.db.QueryRowContext(ctx, createVenue, name, slug, spotifyPlaylist, city) +func (q *Queries) CreateVenue(ctx context.Context, slug string, name string, city string, spotifyPlaylist string, status StatusEnum) (int, error) { + row := q.db.QueryRowContext(ctx, createVenue, slug, name, city, spotifyPlaylist, status) var i int err := row.Scan(&i) return i, err diff --git a/testdata/ondeck/db_test.go b/testdata/ondeck/db_test.go new file mode 100644 index 0000000000..cca5152d80 --- /dev/null +++ b/testdata/ondeck/db_test.go @@ -0,0 +1,284 @@ +package ondeck + +import ( + "context" + "database/sql" + "io/ioutil" + "math/rand" + "path/filepath" + "testing" + + "example.com/ondeck/prepared" + + "github.com/google/go-cmp/cmp" + _ "github.com/lib/pq" +) + +func id() string { + bytes := make([]byte, 10) + for i := 0; i < 10; i++ { + bytes[i] = byte(65 + rand.Intn(25)) //A=65 and Z = 65+25 + } + return string(bytes) +} + +func provision(t *testing.T, source string) (*sql.DB, func()) { + t.Helper() + + db, err := sql.Open("postgres", source) + if err != nil { + t.Fatal(err) + } + + schema := "dinotest_" + id() + + // For each test, pick a new schema name at random. + // `foo` is used here only as an example + if _, err := db.Exec("CREATE SCHEMA " + schema); err != nil { + t.Fatal(err) + } + + sdb, err := sql.Open("postgres", source+"&search_path="+schema) + if err != nil { + t.Fatal(err) + } + + return sdb, func() { + if _, err := db.Exec("DROP SCHEMA " + schema + " CASCADE"); err != nil { + t.Fatal(err) + } + } +} + +func TestQueries(t *testing.T) { + t.Parallel() + + sdb, cleanup := provision(t, "postgres://localhost/dinotest?sslmode=disable") + defer cleanup() + + files, err := ioutil.ReadDir("schema") + if err != nil { + t.Fatal(err) + } + for _, f := range files { + blob, err := ioutil.ReadFile(filepath.Join("schema", f.Name())) + if err != nil { + t.Fatal(err) + } + if _, err := sdb.Exec(string(blob)); err != nil { + t.Fatalf("%s: %s", f.Name(), err) + } + } + + q := New(sdb) + + ctx := context.Background() + + city, err := q.CreateCity(ctx, "san-francisco", "San Francisco") + if err != nil { + t.Fatal(err) + } + + venueID, err := q.CreateVenue(ctx, + "the-fillmore", + "The Fillmore", + city.Slug, + "spotify:uri", + StatusOpen) + if err != nil { + t.Fatal(err) + } + + venue, err := q.GetVenue(ctx, "the-fillmore", city.Slug) + if err != nil { + t.Fatal(err) + } + + if diff := cmp.Diff(venue.ID, venueID); diff != "" { + t.Errorf("venue ID mismatch:\n%s", diff) + } + + { + actual, err := q.GetCity(ctx, city.Slug) + if err != nil { + t.Error(err) + } + if diff := cmp.Diff(actual, city); diff != "" { + t.Errorf("get city mismatch:\n%s", diff) + } + } + + { + actual, err := q.VenueCountByCity(ctx) + if err != nil { + t.Error(err) + } + if diff := cmp.Diff(actual, []VenueCountByCityRow{ + {city.Slug, 1}, + }); diff != "" { + t.Errorf("venue county mismatch:\n%s", diff) + } + } + + { + actual, err := q.ListCities(ctx) + if err != nil { + t.Error(err) + } + if diff := cmp.Diff(actual, []City{city}); diff != "" { + t.Errorf("list city mismatch:\n%s", diff) + } + } + + { + actual, err := q.ListVenues(ctx, city.Slug) + if err != nil { + t.Error(err) + } + if diff := cmp.Diff(actual, []Venue{venue}); diff != "" { + t.Errorf("list venue mismatch:\n%s", diff) + } + } + + { + err := q.UpdateCityName(ctx, city.Slug, "SF") + if err != nil { + t.Error(err) + } + } + + { + count, err := q.UpdateVenueName(ctx, venue.Slug, "Fillmore") + if err != nil { + t.Error(err) + } + if diff := cmp.Diff(count, 1); diff != "" { + t.Errorf("update venue mismatch:\n%s", diff) + } + } + + { + err := q.DeleteVenue(ctx, venue.Slug) + if err != nil { + t.Error(err) + } + } +} + +func TestPrepared(t *testing.T) { + t.Parallel() + + sdb, cleanup := provision(t, "postgres://localhost/dinotest?sslmode=disable") + defer cleanup() + + files, err := ioutil.ReadDir("schema") + if err != nil { + t.Fatal(err) + } + for _, f := range files { + blob, err := ioutil.ReadFile(filepath.Join("schema", f.Name())) + if err != nil { + t.Fatal(err) + } + if _, err := sdb.Exec(string(blob)); err != nil { + t.Fatalf("%s: %s", f.Name(), err) + } + } + + q, err := prepared.Prepare(context.Background(), sdb) + if err != nil { + t.Fatal(err) + } + + ctx := context.Background() + + city, err := q.CreateCity(ctx, "san-francisco", "San Francisco") + if err != nil { + t.Fatal(err) + } + + venueID, err := q.CreateVenue(ctx, + "the-fillmore", + "The Fillmore", + city.Slug, + "spotify:uri", + prepared.StatusOpen) + if err != nil { + t.Fatal(err) + } + + venue, err := q.GetVenue(ctx, "the-fillmore", city.Slug) + if err != nil { + t.Fatal(err) + } + + if diff := cmp.Diff(venue.ID, venueID); diff != "" { + t.Errorf("venue ID mismatch:\n%s", diff) + } + + { + actual, err := q.GetCity(ctx, city.Slug) + if err != nil { + t.Error(err) + } + if diff := cmp.Diff(actual, city); diff != "" { + t.Errorf("get city mismatch:\n%s", diff) + } + } + + { + actual, err := q.VenueCountByCity(ctx) + if err != nil { + t.Error(err) + } + if diff := cmp.Diff(actual, []prepared.VenueCountByCityRow{ + {city.Slug, 1}, + }); diff != "" { + t.Errorf("venue county mismatch:\n%s", diff) + } + } + + { + actual, err := q.ListCities(ctx) + if err != nil { + t.Error(err) + } + if diff := cmp.Diff(actual, []prepared.City{city}); diff != "" { + t.Errorf("list city mismatch:\n%s", diff) + } + } + + { + actual, err := q.ListVenues(ctx, city.Slug) + if err != nil { + t.Error(err) + } + if diff := cmp.Diff(actual, []prepared.Venue{venue}); diff != "" { + t.Errorf("list venue mismatch:\n%s", diff) + } + } + + { + err := q.UpdateCityName(ctx, city.Slug, "SF") + if err != nil { + t.Error(err) + } + } + + { + count, err := q.UpdateVenueName(ctx, venue.Slug, "Fillmore") + if err != nil { + t.Error(err) + } + if diff := cmp.Diff(count, 1); diff != "" { + t.Errorf("update venue mismatch:\n%s", diff) + } + } + + { + err := q.DeleteVenue(ctx, venue.Slug) + if err != nil { + t.Error(err) + } + } +} diff --git a/testdata/ondeck/go.mod b/testdata/ondeck/go.mod new file mode 100644 index 0000000000..82d02c33fa --- /dev/null +++ b/testdata/ondeck/go.mod @@ -0,0 +1,8 @@ +module example.com/ondeck + +go 1.12 + +require ( + github.com/google/go-cmp v0.3.0 + github.com/lib/pq v1.1.1 +) diff --git a/testdata/ondeck/go.sum b/testdata/ondeck/go.sum new file mode 100644 index 0000000000..5070afc22b --- /dev/null +++ b/testdata/ondeck/go.sum @@ -0,0 +1,4 @@ +github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4= +github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= diff --git a/testdata/ondeck/prepared.go b/testdata/ondeck/prepared/prepared.go similarity index 94% rename from testdata/ondeck/prepared.go rename to testdata/ondeck/prepared/prepared.go index a8707bb437..4f36c0bef0 100644 --- a/testdata/ondeck/prepared.go +++ b/testdata/ondeck/prepared/prepared.go @@ -1,23 +1,13 @@ -package ondeck +package prepared import ( "context" "database/sql" - "database/sql/driver" "time" ) type StatusEnum string -func (e *StatusEnum) Scan(v interface{}) error { - *e = StatusEnum(string(v.([]byte))) - return nil -} - -func (e StatusEnum) Value() (driver.Value, error) { - return []byte(string(e)), nil -} - const ( StatusOpen StatusEnum = "open" StatusClosed = "closed" @@ -145,29 +135,31 @@ func (q *Queries) CreateCity(ctx context.Context, name string, slug string) (Cit const createVenue = `-- name: CreateVenue :one INSERT INTO venue ( - name, slug, + name, + city, created_at, spotify_playlist, - city + status ) VALUES ( $1, $2, - NOW(), $3, - $4 + NOW(), + $4, + $5 ) RETURNING id ` -func (q *Queries) CreateVenue(ctx context.Context, name string, slug string, spotifyPlaylist string, city string) (int, error) { +func (q *Queries) CreateVenue(ctx context.Context, slug string, name string, city string, spotifyPlaylist string, status StatusEnum) (int, error) { var row *sql.Row switch { case q.createVenue != nil && q.tx != nil: - row = q.tx.StmtContext(ctx, q.createVenue).QueryRowContext(ctx, name, slug, spotifyPlaylist, city) + row = q.tx.StmtContext(ctx, q.createVenue).QueryRowContext(ctx, slug, name, city, spotifyPlaylist, status) case q.createVenue != nil: - row = q.createVenue.QueryRowContext(ctx, name, slug, spotifyPlaylist, city) + row = q.createVenue.QueryRowContext(ctx, slug, name, city, spotifyPlaylist, status) default: - row = q.db.QueryRowContext(ctx, createVenue, name, slug, spotifyPlaylist, city) + row = q.db.QueryRowContext(ctx, createVenue, slug, name, city, spotifyPlaylist, status) } var i int err := row.Scan(&i) diff --git a/testdata/ondeck/query/venue.sql b/testdata/ondeck/query/venue.sql index 00a8827e51..b4e2d425e9 100644 --- a/testdata/ondeck/query/venue.sql +++ b/testdata/ondeck/query/venue.sql @@ -15,17 +15,19 @@ WHERE slug = $1 AND city = $2; -- name: CreateVenue :one INSERT INTO venue ( - name, slug, + name, + city, created_at, spotify_playlist, - city + status ) VALUES ( $1, $2, - NOW(), $3, - $4 + NOW(), + $4, + $5 ) RETURNING id; -- name: UpdateVenueName :one From 7ad968de4408bcffaa00f7183998336e89f4485a Mon Sep 17 00:00:00 2001 From: Kyle Conroy Date: Thu, 27 Jun 2019 13:11:08 -0700 Subject: [PATCH 2/5] Test everything --- .semaphore/semaphore.yml | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/.semaphore/semaphore.yml b/.semaphore/semaphore.yml index 9befee48ad..81108a251c 100644 --- a/.semaphore/semaphore.yml +++ b/.semaphore/semaphore.yml @@ -7,11 +7,18 @@ agent: type: e1-standard-2 os_image: ubuntu1804 blocks: - - name: Golang example + - name: DinoSQL task: jobs: - - name: Run Go + - name: Setup commands: - checkout - sem-version go 1.12 + - name: Test dinosql + commands: + - go test -v ./... + - name: Test dinosql/ondeck + commands: + - pushd testdata/ondeck - go test -v ./... + - popd From bf6ea7b1d2c34a767d1861ac56505ce19755db69 Mon Sep 17 00:00:00 2001 From: Kyle Conroy Date: Thu, 27 Jun 2019 13:12:31 -0700 Subject: [PATCH 3/5] sigh --- .semaphore/semaphore.yml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/.semaphore/semaphore.yml b/.semaphore/semaphore.yml index 81108a251c..bf7660db70 100644 --- a/.semaphore/semaphore.yml +++ b/.semaphore/semaphore.yml @@ -10,15 +10,14 @@ blocks: - name: DinoSQL task: jobs: - - name: Setup + - name: Test dinosql commands: - checkout - sem-version go 1.12 - - name: Test dinosql - commands: - go test -v ./... - name: Test dinosql/ondeck commands: - - pushd testdata/ondeck + - checkout + - sem-version go 1.12 + - cd testdata/ondeck - go test -v ./... - - popd From 2ee1f36021e205387c3b3562797a0ab6feb06f19 Mon Sep 17 00:00:00 2001 From: Kyle Conroy Date: Thu, 27 Jun 2019 13:16:32 -0700 Subject: [PATCH 4/5] setup db --- .semaphore/semaphore.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.semaphore/semaphore.yml b/.semaphore/semaphore.yml index bf7660db70..168b69068f 100644 --- a/.semaphore/semaphore.yml +++ b/.semaphore/semaphore.yml @@ -17,6 +17,9 @@ blocks: - go test -v ./... - name: Test dinosql/ondeck commands: + - sem-service start postgres + - sudo apt-get install -y -qq postgresql-client + - createdb -U postgres -h 0.0.0.0 dinotest - checkout - sem-version go 1.12 - cd testdata/ondeck From c06e5752bbf967d7665f496fe4a00f4f6f506df7 Mon Sep 17 00:00:00 2001 From: Kyle Conroy Date: Thu, 27 Jun 2019 13:18:43 -0700 Subject: [PATCH 5/5] Use correct user --- testdata/ondeck/db_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/testdata/ondeck/db_test.go b/testdata/ondeck/db_test.go index cca5152d80..de5116ae87 100644 --- a/testdata/ondeck/db_test.go +++ b/testdata/ondeck/db_test.go @@ -53,7 +53,7 @@ func provision(t *testing.T, source string) (*sql.DB, func()) { func TestQueries(t *testing.T) { t.Parallel() - sdb, cleanup := provision(t, "postgres://localhost/dinotest?sslmode=disable") + sdb, cleanup := provision(t, "postgres://postgres:@localhost/dinotest?sslmode=disable") defer cleanup() files, err := ioutil.ReadDir("schema") @@ -168,7 +168,7 @@ func TestQueries(t *testing.T) { func TestPrepared(t *testing.T) { t.Parallel() - sdb, cleanup := provision(t, "postgres://localhost/dinotest?sslmode=disable") + sdb, cleanup := provision(t, "postgres://postgres:@localhost/dinotest?sslmode=disable") defer cleanup() files, err := ioutil.ReadDir("schema")