diff --git a/api/handler.go b/api/handler.go index 20c2a83..ab7bbc7 100644 --- a/api/handler.go +++ b/api/handler.go @@ -59,6 +59,8 @@ func Handler() *mux.Router { m.Get(router.UpdateTextMeasurementType).Handler(handler(serveUpdateTextMeasurementType)) m.Get(router.DeleteTextMeasurementType).Handler(handler(serveDeleteTextMeasurementType)) + m.Get(router.UnitType).Handler(handler(serveUnitType)) + return m } diff --git a/api/unit_types.go b/api/unit_types.go new file mode 100644 index 0000000..ea1fe54 --- /dev/null +++ b/api/unit_types.go @@ -0,0 +1,22 @@ +package api + +import ( + "net/http" + "strconv" + + "github.com/gorilla/mux" +) + +func serveUnitType(w http.ResponseWriter, r *http.Request) error { + id, err := strconv.ParseInt(mux.Vars(r)["Id"], 10, 0) + if err != nil { + return err + } + + unit_type, err := store.UnitTypes.Get(id) + if err != nil { + return err + } + + return writeJSON(w, unit_type) +} diff --git a/api/unit_types_test.go b/api/unit_types_test.go new file mode 100644 index 0000000..c3b5738 --- /dev/null +++ b/api/unit_types_test.go @@ -0,0 +1,40 @@ +package api + +import ( + "testing" + + "github.com/thermokarst/bactdb/models" +) + +func newUnitType() *models.UnitType { + unit_type := models.NewUnitType() + return unit_type +} + +func TestUnitType_Get(t *testing.T) { + setup() + + want := newUnitType() + + calledGet := false + + store.UnitTypes.(*models.MockUnitTypesService).Get_ = func(id int64) (*models.UnitType, error) { + if id != want.Id { + t.Errorf("wanted request for unit_type %d but got %d", want.Id, id) + } + calledGet = true + return want, nil + } + + got, err := apiClient.UnitTypes.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 c4de884..944d0c4 100644 --- a/datastore/datastore.go +++ b/datastore/datastore.go @@ -16,6 +16,7 @@ type Datastore struct { ObservationTypes models.ObservationTypesService Observations models.ObservationsService TextMeasurementTypes models.TextMeasurementTypesService + UnitTypes models.UnitTypesService dbh modl.SqlExecutor } @@ -39,6 +40,7 @@ func NewDatastore(dbh modl.SqlExecutor) *Datastore { d.ObservationTypes = &observationTypesStore{d} d.Observations = &observationsStore{d} d.TextMeasurementTypes = &textMeasurementTypesStore{d} + d.UnitTypes = &unitTypesStore{d} return d } @@ -51,5 +53,6 @@ func NewMockDatastore() *Datastore { ObservationTypes: &models.MockObservationTypesService{}, Observations: &models.MockObservationsService{}, TextMeasurementTypes: &models.MockTextMeasurementTypesService{}, + UnitTypes: &models.MockUnitTypesService{}, } } diff --git a/datastore/unit_types.go b/datastore/unit_types.go new file mode 100644 index 0000000..3c9faec --- /dev/null +++ b/datastore/unit_types.go @@ -0,0 +1,22 @@ +package datastore + +import "github.com/thermokarst/bactdb/models" + +func init() { + DB.AddTableWithName(models.UnitType{}, "unit_types").SetKeys(true, "Id") +} + +type unitTypesStore struct { + *Datastore +} + +func (s *unitTypesStore) Get(id int64) (*models.UnitType, error) { + var unit_type []*models.UnitType + if err := s.dbh.Select(&unit_type, `SELECT * FROM unit_types WHERE id=$1;`, id); err != nil { + return nil, err + } + if len(unit_type) == 0 { + return nil, models.ErrUnitTypeNotFound + } + return unit_type[0], nil +} diff --git a/datastore/unit_types_test.go b/datastore/unit_types_test.go new file mode 100644 index 0000000..178f12a --- /dev/null +++ b/datastore/unit_types_test.go @@ -0,0 +1,44 @@ +package datastore + +import ( + "reflect" + "testing" + + "github.com/jmoiron/modl" + "github.com/thermokarst/bactdb/models" +) + +func insertUnitType(t *testing.T, tx *modl.Transaction) *models.UnitType { + // clean up our target table + tx.Exec(`DELETE FROM unit_types;`) + unit_type := newUnitType(t, tx) + if err := tx.Insert(unit_type); err != nil { + t.Fatal(err) + } + return unit_type +} + +func newUnitType(t *testing.T, tx *modl.Transaction) *models.UnitType { + return &models.UnitType{Name: "Test Unit Type", Symbol: "x"} +} + +func TestUnitTypesStore_Get_db(t *testing.T) { + tx, _ := DB.Begin() + defer tx.Rollback() + + want := insertUnitType(t, tx) + + d := NewDatastore(tx) + + unit_type, err := d.UnitTypes.Get(want.Id) + if err != nil { + t.Fatal(err) + } + + normalizeTime(&want.CreatedAt, &want.UpdatedAt, &want.DeletedAt) + normalizeTime(&unit_type.CreatedAt, &unit_type.UpdatedAt, &unit_type.DeletedAt) + + if !reflect.DeepEqual(unit_type, want) { + t.Errorf("got unit_type %+v, want %+v", unit_type, want) + } +} diff --git a/models/client.go b/models/client.go index df479bb..18420f7 100644 --- a/models/client.go +++ b/models/client.go @@ -23,6 +23,7 @@ type Client struct { ObservationTypes ObservationTypesService Observations ObservationsService TextMeasurementTypes TextMeasurementTypesService + UnitTypes UnitTypesService // BaseURL for HTTP requests to bactdb's API. BaseURL *url.URL @@ -57,6 +58,7 @@ func NewClient(httpClient *http.Client) *Client { c.ObservationTypes = &observationTypesService{c} c.Observations = &observationsService{c} c.TextMeasurementTypes = &textMeasurementTypesService{c} + c.UnitTypes = &unitTypesService{c} return c } diff --git a/models/unit_types.go b/models/unit_types.go new file mode 100644 index 0000000..6dc9597 --- /dev/null +++ b/models/unit_types.go @@ -0,0 +1,75 @@ +package models + +import ( + "errors" + "strconv" + "time" + + "github.com/lib/pq" + "github.com/thermokarst/bactdb/router" +) + +// A UnitType is a lookup type +type UnitType struct { + Id int64 `json:id,omitempty"` + Name string `db:"name" json:"name"` + Symbol string `db:"symbol" json:"symbol"` + CreatedAt time.Time `db:"created_at" json:"createdAt"` + UpdatedAt time.Time `db:"updated_at" json:"updatedAt"` + DeletedAt pq.NullTime `db:"deleted_at" json:"deletedAt"` +} + +func NewUnitType() *UnitType { + return &UnitType{ + Name: "Test Unit Type", + Symbol: "x", + } +} + +type UnitTypesService interface { + // Get a unit type + Get(id int64) (*UnitType, error) +} + +var ( + ErrUnitTypeNotFound = errors.New("unit type not found") +) + +type unitTypesService struct { + client *Client +} + +func (s *unitTypesService) Get(id int64) (*UnitType, error) { + strId := strconv.FormatInt(id, 10) + + url, err := s.client.url(router.UnitType, 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 unit_type *UnitType + _, err = s.client.Do(req, &unit_type) + if err != nil { + return nil, err + } + + return unit_type, nil +} + +type MockUnitTypesService struct { + Get_ func(id int64) (*UnitType, error) +} + +var _ UnitTypesService = &MockUnitTypesService{} + +func (s *MockUnitTypesService) Get(id int64) (*UnitType, error) { + if s.Get_ == nil { + return nil, nil + } + return s.Get_(id) +} diff --git a/models/unit_types_test.go b/models/unit_types_test.go new file mode 100644 index 0000000..b958f43 --- /dev/null +++ b/models/unit_types_test.go @@ -0,0 +1,45 @@ +package models + +import ( + "net/http" + "reflect" + "testing" + + "github.com/thermokarst/bactdb/router" +) + +func newUnitType() *UnitType { + unit_type := NewUnitType() + unit_type.Id = 1 + return unit_type +} + +func TestUnitTypeService_Get(t *testing.T) { + setup() + defer teardown() + + want := newUnitType() + + var called bool + mux.HandleFunc(urlPath(t, router.UnitType, map[string]string{"Id": "1"}), func(w http.ResponseWriter, r *http.Request) { + called = true + testMethod(t, r, "GET") + + writeJSON(w, want) + }) + + unit_type, err := client.UnitTypes.Get(want.Id) + if err != nil { + t.Errorf("UnitTypes.Get returned error: %v", err) + } + + if !called { + t.Fatal("!called") + } + + normalizeTime(&want.CreatedAt, &want.UpdatedAt, &want.DeletedAt) + + if !reflect.DeepEqual(unit_type, want) { + t.Errorf("UnitTypes.Get return %+v, want %+v", unit_type, want) + } +} diff --git a/router/api.go b/router/api.go index 0c6f439..e2b0eff 100644 --- a/router/api.go +++ b/router/api.go @@ -52,5 +52,8 @@ func API() *mux.Router { m.Path("/text_measurement_types/{Id:.+}").Methods("PUT").Name(UpdateTextMeasurementType) m.Path("/text_measurement_types/{Id:.+}").Methods("DELETE").Name(DeleteTextMeasurementType) + // UnitTypes + m.Path("/unit_types/{Id:.+}").Methods("GET").Name(UnitType) + return m } diff --git a/router/routes.go b/router/routes.go index 63a693e..dd80513 100644 --- a/router/routes.go +++ b/router/routes.go @@ -40,4 +40,6 @@ const ( TextMeasurementTypes = "text_measurement_type:list" UpdateTextMeasurementType = "text_measurement_type:update" DeleteTextMeasurementType = "text_measurement_type:delete" + + UnitType = "unit_type:get" )