From a018d2bd7a7bbe76f3f3a17d6ccbaa673841a20b Mon Sep 17 00:00:00 2001 From: Matthew Dillon Date: Tue, 18 Nov 2014 10:41:24 -0900 Subject: [PATCH] Get an observation. --- api/handler.go | 2 + api/observations.go | 22 ++++++++++ api/observations_test.go | 40 ++++++++++++++++++ datastore/datastore.go | 3 ++ datastore/observations.go | 22 ++++++++++ datastore/observations_test.go | 47 +++++++++++++++++++++ models/client.go | 2 + models/observations.go | 74 ++++++++++++++++++++++++++++++++++ models/observations_test.go | 45 +++++++++++++++++++++ router/api.go | 3 ++ router/routes.go | 2 + 11 files changed, 262 insertions(+) create mode 100644 api/observations.go create mode 100644 api/observations_test.go create mode 100644 datastore/observations.go create mode 100644 datastore/observations_test.go create mode 100644 models/observations.go create mode 100644 models/observations_test.go diff --git a/api/handler.go b/api/handler.go index 9a8df48..265f572 100644 --- a/api/handler.go +++ b/api/handler.go @@ -47,6 +47,8 @@ func Handler() *mux.Router { m.Get(router.UpdateObservationType).Handler(handler(serveUpdateObservationType)) m.Get(router.DeleteObservationType).Handler(handler(serveDeleteObservationType)) + m.Get(router.Observation).Handler(handler(serveObservation)) + return m } diff --git a/api/observations.go b/api/observations.go new file mode 100644 index 0000000..f902664 --- /dev/null +++ b/api/observations.go @@ -0,0 +1,22 @@ +package api + +import ( + "net/http" + "strconv" + + "github.com/gorilla/mux" +) + +func serveObservation(w http.ResponseWriter, r *http.Request) error { + id, err := strconv.ParseInt(mux.Vars(r)["Id"], 10, 0) + if err != nil { + return err + } + + observation, err := store.Observations.Get(id) + if err != nil { + return err + } + + return writeJSON(w, observation) +} diff --git a/api/observations_test.go b/api/observations_test.go new file mode 100644 index 0000000..26c8cd4 --- /dev/null +++ b/api/observations_test.go @@ -0,0 +1,40 @@ +package api + +import ( + "testing" + + "github.com/thermokarst/bactdb/models" +) + +func newObservation() *models.Observation { + observation := models.NewObservation() + return observation +} + +func TestObservation_Get(t *testing.T) { + setup() + + want := newObservation() + + calledGet := false + + store.Observations.(*models.MockObservationsService).Get_ = func(id int64) (*models.Observation, error) { + if id != want.Id { + t.Errorf("wanted request for observation %d but got %d", want.Id, id) + } + calledGet = true + return want, nil + } + + got, err := apiClient.Observations.Get(want.Id) + if err != nil { + t.Fatal(err) + } + + if !calledGet { + t.Error("!calledGet") + } + if !normalizeDeepEqual(want, got) { + t.Errorf("got %+v but wanted %+v", got, want) + } +} diff --git a/datastore/datastore.go b/datastore/datastore.go index ace6275..f18afb1 100644 --- a/datastore/datastore.go +++ b/datastore/datastore.go @@ -14,6 +14,7 @@ type Datastore struct { Species models.SpeciesService Strains models.StrainsService ObservationTypes models.ObservationTypesService + Observations models.ObservationsService dbh modl.SqlExecutor } @@ -35,6 +36,7 @@ func NewDatastore(dbh modl.SqlExecutor) *Datastore { d.Species = &speciesStore{d} d.Strains = &strainsStore{d} d.ObservationTypes = &observationTypesStore{d} + d.Observations = &observationsStore{d} return d } @@ -45,5 +47,6 @@ func NewMockDatastore() *Datastore { Species: &models.MockSpeciesService{}, Strains: &models.MockStrainsService{}, ObservationTypes: &models.MockObservationTypesService{}, + Observations: &models.MockObservationsService{}, } } diff --git a/datastore/observations.go b/datastore/observations.go new file mode 100644 index 0000000..e9090b0 --- /dev/null +++ b/datastore/observations.go @@ -0,0 +1,22 @@ +package datastore + +import "github.com/thermokarst/bactdb/models" + +func init() { + DB.AddTableWithName(models.Observation{}, "observations").SetKeys(true, "Id") +} + +type observationsStore struct { + *Datastore +} + +func (s *observationsStore) Get(id int64) (*models.Observation, error) { + var observation []*models.Observation + if err := s.dbh.Select(&observation, `SELECT * FROM observations WHERE id=$1;`, id); err != nil { + return nil, err + } + if len(observation) == 0 { + return nil, models.ErrObservationNotFound + } + return observation[0], nil +} diff --git a/datastore/observations_test.go b/datastore/observations_test.go new file mode 100644 index 0000000..eae9ee3 --- /dev/null +++ b/datastore/observations_test.go @@ -0,0 +1,47 @@ +package datastore + +import ( + "reflect" + "testing" + + "github.com/jmoiron/modl" + "github.com/thermokarst/bactdb/models" +) + +func insertObservation(t *testing.T, tx *modl.Transaction) *models.Observation { + // clean up our target table + tx.Exec(`DELETE FROM observations;`) + observation := newObservation(t, tx) + if err := tx.Insert(observation); err != nil { + t.Fatal(err) + } + return observation +} + +func newObservation(t *testing.T, tx *modl.Transaction) *models.Observation { + // we want to create and insert an observation type record, too. + observation_type := insertObservationType(t, tx) + return &models.Observation{ObservationName: "Test Observation", + ObservationTypeId: observation_type.Id} +} + +func TestObservationsStore_Get_db(t *testing.T) { + tx, _ := DB.Begin() + defer tx.Rollback() + + want := insertObservation(t, tx) + + d := NewDatastore(tx) + + observation, err := d.Observations.Get(want.Id) + if err != nil { + t.Fatal(err) + } + + normalizeTime(&want.CreatedAt, &want.UpdatedAt, &want.DeletedAt) + normalizeTime(&observation.CreatedAt, &observation.UpdatedAt, &observation.DeletedAt) + + if !reflect.DeepEqual(observation, want) { + t.Errorf("got observation %+v, want %+v", observation, want) + } +} diff --git a/models/client.go b/models/client.go index 12c6a82..e51c379 100644 --- a/models/client.go +++ b/models/client.go @@ -21,6 +21,7 @@ type Client struct { Species SpeciesService Strains StrainsService ObservationTypes ObservationTypesService + Observations ObservationsService // BaseURL for HTTP requests to bactdb's API. BaseURL *url.URL @@ -53,6 +54,7 @@ func NewClient(httpClient *http.Client) *Client { c.Species = &speciesService{c} c.Strains = &strainsService{c} c.ObservationTypes = &observationTypesService{c} + c.Observations = &observationsService{c} return c } diff --git a/models/observations.go b/models/observations.go new file mode 100644 index 0000000..95971ad --- /dev/null +++ b/models/observations.go @@ -0,0 +1,74 @@ +package models + +import ( + "errors" + "strconv" + "time" + + "github.com/lib/pq" + "github.com/thermokarst/bactdb/router" +) + +// An Observation is a lookup type +type Observation struct { + Id int64 `json:"id,omitempty"` + ObservationName string `db:"observation_name" json:"observation_name"` + ObservationTypeId int64 `db:"observation_type_id" json:"observation_type_id"` + CreatedAt time.Time `db:"created_at" json:"created_at"` + UpdatedAt time.Time `db:"updated_at" json:"updated_at"` + DeletedAt pq.NullTime `db:"deleted_at" json:"deleted_at"` +} + +func NewObservation() *Observation { + return &Observation{ + ObservationName: "Test Observation", + } +} + +type ObservationsService interface { + // Get an observation + Get(id int64) (*Observation, error) +} + +var ( + ErrObservationNotFound = errors.New("observation not found") +) + +type observationsService struct { + client *Client +} + +func (s *observationsService) Get(id int64) (*Observation, error) { + strId := strconv.FormatInt(id, 10) + + url, err := s.client.url(router.Observation, map[string]string{"Id": strId}, nil) + if err != nil { + return nil, err + } + + req, err := s.client.NewRequest("GET", url.String(), nil) + if err != nil { + return nil, err + } + + var observation *Observation + _, err = s.client.Do(req, &observation) + if err != nil { + return nil, err + } + + return observation, nil +} + +type MockObservationsService struct { + Get_ func(id int64) (*Observation, error) +} + +var _ObservationsService = &MockObservationsService{} + +func (s *MockObservationsService) Get(id int64) (*Observation, error) { + if s.Get_ == nil { + return nil, nil + } + return s.Get_(id) +} diff --git a/models/observations_test.go b/models/observations_test.go new file mode 100644 index 0000000..d3069ea --- /dev/null +++ b/models/observations_test.go @@ -0,0 +1,45 @@ +package models + +import ( + "net/http" + "reflect" + "testing" + + "github.com/thermokarst/bactdb/router" +) + +func newObservation() *Observation { + observation := NewObservation() + observation.Id = 1 + return observation +} + +func TestObservationService_Get(t *testing.T) { + setup() + defer teardown() + + want := newObservation() + + var called bool + mux.HandleFunc(urlPath(t, router.Observation, map[string]string{"Id": "1"}), func(w http.ResponseWriter, r *http.Request) { + called = true + testMethod(t, r, "GET") + + writeJSON(w, want) + }) + + observation, err := client.Observations.Get(want.Id) + if err != nil { + t.Errorf("Observations.Get returned error: %v", err) + } + + if !called { + t.Fatal("!called") + } + + normalizeTime(&want.CreatedAt, &want.UpdatedAt, &want.DeletedAt) + + if !reflect.DeepEqual(observation, want) { + t.Errorf("Observations.Get return %+v, want %+v", observation, want) + } +} diff --git a/router/api.go b/router/api.go index e3d5cb7..a5f8ddb 100644 --- a/router/api.go +++ b/router/api.go @@ -38,5 +38,8 @@ func API() *mux.Router { m.Path("/observation_types/{Id:.+}").Methods("PUT").Name(UpdateObservationType) m.Path("/observation_types/{Id:.+}").Methods("DELETE").Name(DeleteObservationType) + // Observations + m.Path("/observations/{Id:.+}").Methods("GET").Name(Observation) + return m } diff --git a/router/routes.go b/router/routes.go index 7557d9a..5d4e6eb 100644 --- a/router/routes.go +++ b/router/routes.go @@ -28,4 +28,6 @@ const ( ObservationTypes = "observation_type:list" UpdateObservationType = "observation_type:update" DeleteObservationType = "observation_type:delete" + + Observation = "observation:get" )