From 7fe5566edfd22068c1b0394c130f8e4622080cd2 Mon Sep 17 00:00:00 2001 From: Matthew Dillon Date: Wed, 15 Oct 2014 16:43:09 -0800 Subject: [PATCH] Create species, cleanup schema. - create species - species genus_id not null --- api/handler.go | 1 + api/species.go | 20 +++++++++++ api/species_test.go | 27 +++++++++++++++ datastore/migrations/00003_AddSpecies_up.sql | 2 +- datastore/species.go | 7 ++++ datastore/species_test.go | 28 +++++++++++++++ models/species.go | 33 +++++++++++++++++- models/species_test.go | 36 ++++++++++++++++++++ router/api.go | 1 + router/routes.go | 3 +- 10 files changed, 155 insertions(+), 3 deletions(-) diff --git a/api/handler.go b/api/handler.go index 384740a..65bbc79 100644 --- a/api/handler.go +++ b/api/handler.go @@ -30,6 +30,7 @@ func Handler() *mux.Router { m.Get(router.DeleteGenus).Handler(handler(serveDeleteGenus)) m.Get(router.Species).Handler(handler(serveSpecies)) + m.Get(router.CreateSpecies).Handler(handler(serveCreateSpecies)) return m } diff --git a/api/species.go b/api/species.go index c8e127c..b8bae18 100644 --- a/api/species.go +++ b/api/species.go @@ -1,10 +1,12 @@ package api import ( + "encoding/json" "net/http" "strconv" "github.com/gorilla/mux" + "github.com/thermokarst/bactdb/models" ) func serveSpecies(w http.ResponseWriter, r *http.Request) error { @@ -20,3 +22,21 @@ func serveSpecies(w http.ResponseWriter, r *http.Request) error { return writeJSON(w, species) } + +func serveCreateSpecies(w http.ResponseWriter, r *http.Request) error { + var species models.Species + err := json.NewDecoder(r.Body).Decode(&species) + if err != nil { + return err + } + + created, err := store.Species.Create(&species) + if err != nil { + return err + } + if created { + w.WriteHeader(http.StatusCreated) + } + + return writeJSON(w, species) +} diff --git a/api/species_test.go b/api/species_test.go index 407a952..24fd557 100644 --- a/api/species_test.go +++ b/api/species_test.go @@ -32,3 +32,30 @@ func TestSpecies_Get(t *testing.T) { t.Errorf("got species %+v but wanted species %+v", got, want) } } + +func TestSpecies_Create(t *testing.T) { + setup() + + want := &models.Species{Id: 1, GenusId: 1, SpeciesName: "Test Species"} + + calledPost := false + store.Species.(*models.MockSpeciesService).Create_ = func(species *models.Species) (bool, error) { + if !normalizeDeepEqual(want, species) { + t.Errorf("wanted request for species %d but got %d", want, species) + } + calledPost = true + return true, nil + } + + success, err := apiClient.Species.Create(want) + if err != nil { + t.Fatal(err) + } + + if !calledPost { + t.Error("!calledPost") + } + if !success { + t.Error("!success") + } +} diff --git a/datastore/migrations/00003_AddSpecies_up.sql b/datastore/migrations/00003_AddSpecies_up.sql index d290de1..aff073b 100644 --- a/datastore/migrations/00003_AddSpecies_up.sql +++ b/datastore/migrations/00003_AddSpecies_up.sql @@ -3,7 +3,7 @@ CREATE TABLE species ( id BIGSERIAL NOT NULL, - genusid BIGINT, + genusid BIGINT NOT NULL, speciesname CHARACTER VARYING(100), createdat TIMESTAMP WITH TIME ZONE, diff --git a/datastore/species.go b/datastore/species.go index a7f3c17..f7e6dee 100644 --- a/datastore/species.go +++ b/datastore/species.go @@ -20,3 +20,10 @@ func (s *speciesStore) Get(id int64) (*models.Species, error) { } return species[0], nil } + +func (s *speciesStore) Create(species *models.Species) (bool, error) { + if err := s.dbh.Insert(species); err != nil { + return false, err + } + return true, nil +} diff --git a/datastore/species_test.go b/datastore/species_test.go index 915d522..db81db6 100644 --- a/datastore/species_test.go +++ b/datastore/species_test.go @@ -35,3 +35,31 @@ func TestSpeciesStore_Get_db(t *testing.T) { t.Errorf("got species %+v, want %+v", species, want) } } + +func TestSpeciesStore_Create_db(t *testing.T) { + tx, _ := DB.Begin() + defer tx.Rollback() + + // Test on a clean database + tx.Exec(`DELETE FROM species;`) + + genus := &models.Genus{} + if err := tx.Insert(genus); err != nil { + t.Fatal(err) + } + + species := &models.Species{Id: 1, GenusId: genus.Id, SpeciesName: "Test Species"} + + d := NewDatastore(tx) + created, err := d.Species.Create(species) + if err != nil { + t.Fatal(err) + } + + if !created { + t.Error("!created") + } + if species.Id == 0 { + t.Error("want nonzero species.Id after submitting") + } +} diff --git a/models/species.go b/models/species.go index 336c474..3437d8e 100644 --- a/models/species.go +++ b/models/species.go @@ -2,6 +2,7 @@ package models import ( "errors" + "net/http" "strconv" "time" @@ -22,6 +23,9 @@ type Species struct { type SpeciesService interface { // Get a species Get(id int64) (*Species, error) + + // Create a species record + Create(species *Species) (bool, error) } var ( @@ -55,8 +59,28 @@ func (s *speciesService) Get(id int64) (*Species, error) { return species, nil } +func (s *speciesService) Create(species *Species) (bool, error) { + url, err := s.client.url(router.CreateSpecies, nil, nil) + if err != nil { + return false, err + } + + req, err := s.client.NewRequest("POST", url.String(), species) + if err != nil { + return false, err + } + + resp, err := s.client.Do(req, &species) + if err != nil { + return false, err + } + + return resp.StatusCode == http.StatusCreated, nil +} + type MockSpeciesService struct { - Get_ func(id int64) (*Species, error) + Get_ func(id int64) (*Species, error) + Create_ func(species *Species) (bool, error) } var _ SpeciesService = &MockSpeciesService{} @@ -67,3 +91,10 @@ func (s *MockSpeciesService) Get(id int64) (*Species, error) { } return s.Get_(id) } + +func (s *MockSpeciesService) Create(species *Species) (bool, error) { + if s.Create_ == nil { + return false, nil + } + return s.Create_(species) +} diff --git a/models/species_test.go b/models/species_test.go index e080179..338d13b 100644 --- a/models/species_test.go +++ b/models/species_test.go @@ -37,3 +37,39 @@ func TestSpeciesService_Get(t *testing.T) { t.Errorf("Species.Get returned %+v, want %+v", species, want) } } + +func TestSpeciesService_Create(t *testing.T) { + setup() + defer teardown() + + want := &Species{Id: 1, GenusId: 1, SpeciesName: "Test Species"} + + var called bool + mux.HandleFunc(urlPath(t, router.CreateSpecies, nil), func(w http.ResponseWriter, r *http.Request) { + called = true + testMethod(t, r, "POST") + testBody(t, r, `{"id":1,"genus_id":1,"species_name":"Test Species","created_at":"0001-01-01T00:00:00Z","updated_at":"0001-01-01T00:00:00Z","deleted_at":"0001-01-01T00:00:00Z"}`+"\n") + + w.WriteHeader(http.StatusCreated) + writeJSON(w, want) + }) + + species := &Species{Id: 1, GenusId: 1, SpeciesName: "Test Species"} + created, err := client.Species.Create(species) + if err != nil { + t.Errorf("Species.Create returned error: %v", err) + } + + if !created { + t.Error("!created") + } + + if !called { + t.Fatal("!called") + } + + normalizeTime(&want.CreatedAt, &want.UpdatedAt, &want.DeletedAt) + if !reflect.DeepEqual(species, want) { + t.Errorf("Species.Create returned %+v, want %+v", species, want) + } +} diff --git a/router/api.go b/router/api.go index 229bddc..8ae8b02 100644 --- a/router/api.go +++ b/router/api.go @@ -18,6 +18,7 @@ func API() *mux.Router { m.Path("/genera/{Id:.+}").Methods("DELETE").Name(DeleteGenus) // Species + m.Path("/species").Methods("POST").Name(CreateSpecies) m.Path("/species/{Id:.+}").Methods("GET").Name(Species) return m diff --git a/router/routes.go b/router/routes.go index 9c4ed37..8b2bd70 100644 --- a/router/routes.go +++ b/router/routes.go @@ -11,5 +11,6 @@ const ( UpdateGenus = "genus:update" DeleteGenus = "genus:delete" - Species = "species" + Species = "species" + CreateSpecies = "species:create" )