From 279651930e208309b65cfd62fe4b3aad624006e8 Mon Sep 17 00:00:00 2001 From: Matthew Dillon Date: Mon, 13 Oct 2014 15:26:35 -0800 Subject: [PATCH] A few big changes - Adding timezone to source config - genera: update record (missed a few pieces) - genera: delete record --- api/genera.go | 14 ++++++++++ api/genera_test.go | 27 ++++++++++++++++++ api/handler.go | 1 + datastore/datastore.go | 7 +++++ datastore/db.go | 2 +- datastore/genera.go | 31 +++++++++++++++++++-- datastore/genera_test.go | 59 ++++++++++++++++++++++++++++++++++++++++ models/genera.go | 33 ++++++++++++++++++++++ models/genera_test.go | 29 ++++++++++++++++++++ router/api.go | 1 + router/routes.go | 1 + test.sh | 2 +- 12 files changed, 203 insertions(+), 4 deletions(-) diff --git a/api/genera.go b/api/genera.go index 6d71264..e34196a 100644 --- a/api/genera.go +++ b/api/genera.go @@ -78,3 +78,17 @@ func serveUpdateGenus(w http.ResponseWriter, r *http.Request) error { return writeJSON(w, genus) } + +func serveDeleteGenus(w http.ResponseWriter, r *http.Request) error { + id, _ := strconv.ParseInt(mux.Vars(r)["Id"], 10, 0) + + deleted, err := store.Genera.Delete(id) + if err != nil { + return err + } + if deleted { + w.WriteHeader(http.StatusOK) + } + + return writeJSON(w, &models.Genus{}) +} diff --git a/api/genera_test.go b/api/genera_test.go index d99da53..c5b1949 100644 --- a/api/genera_test.go +++ b/api/genera_test.go @@ -118,3 +118,30 @@ func TestGenus_Update(t *testing.T) { t.Error("!success") } } + +func TestGenus_Delete(t *testing.T) { + setup() + + want := &models.Genus{Id: 1, GenusName: "Test Genus"} + + calledDelete := false + store.Genera.(*models.MockGeneraService).Delete_ = func(id int64) (bool, error) { + if id != want.Id { + t.Errorf("wanted request for genus %d but got %d", want.Id, id) + } + calledDelete = true + return true, nil + } + + success, err := apiClient.Genera.Delete(1) + if err != nil { + t.Fatal(err) + } + + if !calledDelete { + t.Error("!calledDelete") + } + if !success { + t.Error("!success") + } +} diff --git a/api/handler.go b/api/handler.go index e66546f..a01506b 100644 --- a/api/handler.go +++ b/api/handler.go @@ -27,6 +27,7 @@ func Handler() *mux.Router { m.Get(router.CreateGenus).Handler(handler(serveCreateGenus)) m.Get(router.Genera).Handler(handler(serveGenera)) m.Get(router.UpdateGenus).Handler(handler(serveUpdateGenus)) + m.Get(router.DeleteGenus).Handler(handler(serveDeleteGenus)) return m } diff --git a/datastore/datastore.go b/datastore/datastore.go index 94ff786..43faafd 100644 --- a/datastore/datastore.go +++ b/datastore/datastore.go @@ -1,6 +1,8 @@ package datastore import ( + "errors" + "github.com/jmoiron/modl" "github.com/thermokarst/bactdb/models" ) @@ -12,6 +14,11 @@ type Datastore struct { dbh modl.SqlExecutor } +var ( + ErrNoRowsUpdated = errors.New(`no rows updated`) + ErrNoRowsDeleted = errors.New(`no rows deleted`) +) + // NewDatastore creates a new client for accessing the datastore (in PostgreSQL). // If dbh is nil, it uses the global DB handle. func NewDatastore(dbh modl.SqlExecutor) *Datastore { diff --git a/datastore/db.go b/datastore/db.go index e8a6b93..71d613e 100644 --- a/datastore/db.go +++ b/datastore/db.go @@ -26,7 +26,7 @@ var connectOnce sync.Once func Connect() { connectOnce.Do(func() { var err error - DB.Dbx, err = sqlx.Open("postgres", "sslmode=disable") + DB.Dbx, err = sqlx.Open("postgres", "timezone=UTC sslmode=disable") if err != nil { log.Fatal("Error connecting to PostgreSQL database (using PG* environment variables): ", err) } diff --git a/datastore/genera.go b/datastore/genera.go index f492f50..637c58b 100644 --- a/datastore/genera.go +++ b/datastore/genera.go @@ -47,12 +47,39 @@ func (s *generaStore) List(opt *models.GenusListOptions) ([]*models.Genus, error } func (s *generaStore) Update(id int64, genus *models.Genus) (bool, error) { - ret, err := s.dbh.Exec(`UPDATE genera SET genusname=$1 WHERE id=$2;`, genus.GenusName, id) + _, err := s.Get(id) if err != nil { return false, err } - if rows, err := ret.RowsAffected(); rows == 0 { + + if id != genus.Id { + return false, models.ErrGenusNotFound + } + + changed, err := s.dbh.Update(genus) + if err != nil { return false, err } + + if changed == 0 { + return false, ErrNoRowsUpdated + } + + return true, nil +} + +func (s *generaStore) Delete(id int64) (bool, error) { + genus, err := s.Get(id) + if err != nil { + return false, err + } + + deleted, err := s.dbh.Delete(genus) + if err != nil { + return false, err + } + if deleted == 0 { + return false, ErrNoRowsDeleted + } return true, nil } diff --git a/datastore/genera_test.go b/datastore/genera_test.go index 5eebae2..32b9d77 100644 --- a/datastore/genera_test.go +++ b/datastore/genera_test.go @@ -79,3 +79,62 @@ func TestGeneraStore_List_db(t *testing.T) { t.Errorf("got genera %+v, want %+v", genera, want) } } + +func TestGeneraStore_Update_db(t *testing.T) { + tx, _ := DB.Begin() + defer tx.Rollback() + + // Test on a clean database + tx.Exec(`DELETE FROM genera;`) + + d := NewDatastore(nil) + // Add a new record + genus := &models.Genus{GenusName: "Test Genus"} + created, err := d.Genera.Create(genus) + if err != nil { + t.Fatal(err) + } + if !created { + t.Error("!created") + } + + // Tweak it + genus.GenusName = "Updated Genus" + updated, err := d.Genera.Update(genus.Id, genus) + if err != nil { + t.Fatal(err) + } + + if !updated { + t.Error("!updated") + } +} + +func TestGeneraStore_Delete_db(t *testing.T) { + tx, _ := DB.Begin() + defer tx.Rollback() + + // Test on a clean database + tx.Exec(`DELETE FROM genera;`) + + d := NewDatastore(tx) + // Add a new record + genus := &models.Genus{GenusName: "Test Genus"} + created, err := d.Genera.Create(genus) + if err != nil { + t.Fatal(err) + } + if !created { + t.Error("!created") + } + + // Delete it + deleted, err := d.Genera.Delete(genus.Id) + if err != nil { + t.Fatal(err) + } + + if !deleted { + t.Error("!delete") + } +} diff --git a/models/genera.go b/models/genera.go index d6be8ee..fb87694 100644 --- a/models/genera.go +++ b/models/genera.go @@ -31,6 +31,9 @@ type GeneraService interface { // Update an existing genus. Update(id int64, genus *Genus) (updated bool, err error) + + // Delete an existing genus. + Delete(id int64) (deleted bool, err error) } var ( @@ -129,11 +132,34 @@ func (s *generaService) Update(id int64, genus *Genus) (bool, error) { return resp.StatusCode == http.StatusOK, nil } +func (s *generaService) Delete(id int64) (bool, error) { + strId := strconv.FormatInt(id, 10) + + url, err := s.client.url(router.DeleteGenus, map[string]string{"Id": strId}, nil) + if err != nil { + return false, err + } + + req, err := s.client.NewRequest("DELETE", url.String(), nil) + if err != nil { + return false, err + } + + var genus *Genus + resp, err := s.client.Do(req, &genus) + if err != nil { + return false, err + } + + return resp.StatusCode == http.StatusOK, nil +} + type MockGeneraService struct { Get_ func(id int64) (*Genus, error) List_ func(opt *GenusListOptions) ([]*Genus, error) Create_ func(genus *Genus) (bool, error) Update_ func(id int64, genus *Genus) (bool, error) + Delete_ func(id int64) (bool, error) } var _ GeneraService = &MockGeneraService{} @@ -165,3 +191,10 @@ func (s *MockGeneraService) Update(id int64, genus *Genus) (bool, error) { } return s.Update_(id, genus) } + +func (s *MockGeneraService) Delete(id int64) (bool, error) { + if s.Delete_ == nil { + return false, nil + } + return s.Delete_(id) +} diff --git a/models/genera_test.go b/models/genera_test.go index 2374a10..453f0bf 100644 --- a/models/genera_test.go +++ b/models/genera_test.go @@ -136,3 +136,32 @@ func TestGeneraService_Update(t *testing.T) { t.Fatal("!called") } } + +func TestGeneraService_Delete(t *testing.T) { + setup() + defer teardown() + + want := &Genus{Id: 1, GenusName: "Test Genus"} + + var called bool + mux.HandleFunc(urlPath(t, router.DeleteGenus, map[string]string{"Id": "1"}), func(w http.ResponseWriter, r *http.Request) { + called = true + testMethod(t, r, "DELETE") + + w.WriteHeader(http.StatusOK) + writeJSON(w, want) + }) + + deleted, err := client.Genera.Delete(1) + if err != nil { + t.Errorf("Genera.Delete returned error: %v", err) + } + + if !deleted { + t.Error("!deleted") + } + + if !called { + t.Fatal("!called") + } +} diff --git a/router/api.go b/router/api.go index bf5a396..1df34ee 100644 --- a/router/api.go +++ b/router/api.go @@ -15,5 +15,6 @@ func API() *mux.Router { m.Path("/genera").Methods("POST").Name(CreateGenus) m.Path("/genera/{Id:.+}").Methods("GET").Name(Genus) m.Path("/genera/{Id:.+}").Methods("PUT").Name(UpdateGenus) + m.Path("/genera/{Id:.+}").Methods("DELETE").Name(DeleteGenus) return m } diff --git a/router/routes.go b/router/routes.go index ef4d7d8..e3bb7bb 100644 --- a/router/routes.go +++ b/router/routes.go @@ -9,4 +9,5 @@ const ( CreateGenus = "genus:create" Genera = "genera" UpdateGenus = "genus:update" + DeleteGenus = "genus:delete" ) diff --git a/test.sh b/test.sh index a20af0e..89a7703 100755 --- a/test.sh +++ b/test.sh @@ -1,4 +1,4 @@ #!/usr/bin/env bash -PGTZ=UTC go test -v ./... +go test -v ./...