From 950b15a1176c9e6b3f84ccb90fa22779ac44c47b Mon Sep 17 00:00:00 2001 From: Matthew Dillon Date: Fri, 12 Dec 2014 10:34:56 -0900 Subject: [PATCH] Renaming observations to characteristics --- api/characteristics.go | 92 ++++++++ api/characteristics_test.go | 153 +++++++++++++ api/handler.go | 10 +- api/measurements_test.go | 2 +- api/observations.go | 92 -------- api/observations_test.go | 153 ------------- datastore/characteristic_types_test.go | 4 +- datastore/characteristics.go | 87 ++++++++ datastore/characteristics_test.go | 129 +++++++++++ datastore/datastore.go | 10 +- datastore/measurements_test.go | 10 +- ....sql => 00006_AddCharacteristics_down.sql} | 2 +- ...up.sql => 00006_AddCharacteristics_up.sql} | 8 +- .../migrations/00010_AddMeasurements_up.sql | 8 +- datastore/observations.go | 87 -------- datastore/observations_test.go | 129 ----------- models/characteristic_types.go | 2 +- models/characteristic_types_test.go | 6 +- models/characteristics.go | 204 ++++++++++++++++++ models/characteristics_test.go | 174 +++++++++++++++ models/client.go | 6 +- models/measurements.go | 4 +- models/measurements_test.go | 6 +- models/observations.go | 204 ------------------ models/observations_test.go | 174 --------------- router/api.go | 12 +- router/routes.go | 10 +- 27 files changed, 889 insertions(+), 889 deletions(-) create mode 100644 api/characteristics.go create mode 100644 api/characteristics_test.go delete mode 100644 api/observations.go delete mode 100644 api/observations_test.go create mode 100644 datastore/characteristics.go create mode 100644 datastore/characteristics_test.go rename datastore/migrations/{00006_AddObservations_down.sql => 00006_AddCharacteristics_down.sql} (53%) rename datastore/migrations/{00006_AddObservations_up.sql => 00006_AddCharacteristics_up.sql} (59%) delete mode 100644 datastore/observations.go delete mode 100644 datastore/observations_test.go create mode 100644 models/characteristics.go create mode 100644 models/characteristics_test.go delete mode 100644 models/observations.go delete mode 100644 models/observations_test.go diff --git a/api/characteristics.go b/api/characteristics.go new file mode 100644 index 0000000..688e5a0 --- /dev/null +++ b/api/characteristics.go @@ -0,0 +1,92 @@ +package api + +import ( + "encoding/json" + "net/http" + "strconv" + + "github.com/gorilla/mux" + "github.com/thermokarst/bactdb/models" +) + +func serveCharacteristic(w http.ResponseWriter, r *http.Request) error { + id, err := strconv.ParseInt(mux.Vars(r)["Id"], 10, 0) + if err != nil { + return err + } + + characteristic, err := store.Characteristics.Get(id) + if err != nil { + return err + } + + return writeJSON(w, characteristic) +} + +func serveCreateCharacteristic(w http.ResponseWriter, r *http.Request) error { + var characteristic models.Characteristic + err := json.NewDecoder(r.Body).Decode(&characteristic) + if err != nil { + return err + } + + created, err := store.Characteristics.Create(&characteristic) + if err != nil { + return err + } + if created { + w.WriteHeader(http.StatusCreated) + } + + return writeJSON(w, characteristic) +} + +func serveCharacteristicList(w http.ResponseWriter, r *http.Request) error { + var opt models.CharacteristicListOptions + if err := schemaDecoder.Decode(&opt, r.URL.Query()); err != nil { + return err + } + + characteristics, err := store.Characteristics.List(&opt) + if err != nil { + return err + } + if characteristics == nil { + characteristics = []*models.Characteristic{} + } + + return writeJSON(w, characteristics) +} + +func serveUpdateCharacteristic(w http.ResponseWriter, r *http.Request) error { + id, _ := strconv.ParseInt(mux.Vars(r)["Id"], 10, 0) + var characteristic models.Characteristic + err := json.NewDecoder(r.Body).Decode(&characteristic) + if err != nil { + return err + } + + updated, err := store.Characteristics.Update(id, &characteristic) + if err != nil { + return err + } + if updated { + w.WriteHeader(http.StatusOK) + } + + return writeJSON(w, characteristic) +} + +func serveDeleteCharacteristic(w http.ResponseWriter, r *http.Request) error { + id, _ := strconv.ParseInt(mux.Vars(r)["Id"], 10, 0) + + deleted, err := store.Characteristics.Delete(id) + if err != nil { + return err + } + if deleted { + w.WriteHeader(http.StatusOK) + } + + return writeJSON(w, &models.Characteristic{}) +} diff --git a/api/characteristics_test.go b/api/characteristics_test.go new file mode 100644 index 0000000..75f4bf7 --- /dev/null +++ b/api/characteristics_test.go @@ -0,0 +1,153 @@ +package api + +import ( + "testing" + + "github.com/thermokarst/bactdb/models" +) + +func newCharacteristic() *models.Characteristic { + characteristic := models.NewCharacteristic() + return characteristic +} + +func TestCharacteristic_Get(t *testing.T) { + setup() + + want := newCharacteristic() + + calledGet := false + + store.Characteristics.(*models.MockCharacteristicsService).Get_ = func(id int64) (*models.Characteristic, error) { + if id != want.Id { + t.Errorf("wanted request for characteristic %d but got %d", want.Id, id) + } + calledGet = true + return want, nil + } + + got, err := apiClient.Characteristics.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) + } +} + +func TestCharacteristic_Create(t *testing.T) { + setup() + + want := newCharacteristic() + + calledPost := false + store.Characteristics.(*models.MockCharacteristicsService).Create_ = func(characteristic *models.Characteristic) (bool, error) { + if !normalizeDeepEqual(want, characteristic) { + t.Errorf("wanted request for characteristic %d but got %d", want, characteristic) + } + calledPost = true + return true, nil + } + + success, err := apiClient.Characteristics.Create(want) + if err != nil { + t.Fatal(err) + } + + if !calledPost { + t.Error("!calledPost") + } + if !success { + t.Error("!success") + } +} + +func TestCharacteristic_List(t *testing.T) { + setup() + + want := []*models.Characteristic{newCharacteristic()} + wantOpt := &models.CharacteristicListOptions{ListOptions: models.ListOptions{Page: 1, PerPage: 10}} + + calledList := false + store.Characteristics.(*models.MockCharacteristicsService).List_ = func(opt *models.CharacteristicListOptions) ([]*models.Characteristic, error) { + if !normalizeDeepEqual(wantOpt, opt) { + t.Errorf("wanted options %d but got %d", wantOpt, opt) + } + calledList = true + return want, nil + } + + characteristics, err := apiClient.Characteristics.List(wantOpt) + if err != nil { + t.Fatal(err) + } + + if !calledList { + t.Error("!calledList") + } + + if !normalizeDeepEqual(&want, &characteristics) { + t.Errorf("got characteristics %+v but wanted characteristics %+v", characteristics, want) + } +} + +func TestCharacteristic_Update(t *testing.T) { + setup() + + want := newCharacteristic() + + calledPut := false + store.Characteristics.(*models.MockCharacteristicsService).Update_ = func(id int64, characteristic *models.Characteristic) (bool, error) { + if id != want.Id { + t.Errorf("wanted request for characteristic %d but got %d", want.Id, id) + } + if !normalizeDeepEqual(want, characteristic) { + t.Errorf("wanted request for characteristic %d but got %d", want, characteristic) + } + calledPut = true + return true, nil + } + + success, err := apiClient.Characteristics.Update(want.Id, want) + if err != nil { + t.Fatal(err) + } + + if !calledPut { + t.Error("!calledPut") + } + if !success { + t.Error("!success") + } +} + +func TestCharacteristic_Delete(t *testing.T) { + setup() + + want := newCharacteristic() + + calledDelete := false + store.Characteristics.(*models.MockCharacteristicsService).Delete_ = func(id int64) (bool, error) { + if id != want.Id { + t.Errorf("wanted request for characteristic %d but got %d", want.Id, id) + } + calledDelete = true + return true, nil + } + + success, err := apiClient.Characteristics.Delete(want.Id) + 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 a50cd18..c13746a 100644 --- a/api/handler.go +++ b/api/handler.go @@ -45,11 +45,11 @@ func Handler() *mux.Router { m.Get(router.UpdateCharacteristicType).Handler(handler(serveUpdateCharacteristicType)) m.Get(router.DeleteCharacteristicType).Handler(handler(serveDeleteCharacteristicType)) - m.Get(router.Observation).Handler(handler(serveObservation)) - m.Get(router.CreateObservation).Handler(handler(serveCreateObservation)) - m.Get(router.Observations).Handler(handler(serveObservationList)) - m.Get(router.UpdateObservation).Handler(handler(serveUpdateObservation)) - m.Get(router.DeleteObservation).Handler(handler(serveDeleteObservation)) + m.Get(router.Characteristic).Handler(handler(serveCharacteristic)) + m.Get(router.CreateCharacteristic).Handler(handler(serveCreateCharacteristic)) + m.Get(router.Characteristics).Handler(handler(serveCharacteristicList)) + m.Get(router.UpdateCharacteristic).Handler(handler(serveUpdateCharacteristic)) + m.Get(router.DeleteCharacteristic).Handler(handler(serveDeleteCharacteristic)) m.Get(router.TextMeasurementType).Handler(handler(serveTextMeasurementType)) m.Get(router.CreateTextMeasurementType).Handler(handler(serveCreateTextMeasurementType)) diff --git a/api/measurements_test.go b/api/measurements_test.go index a1235e6..c0b6b83 100644 --- a/api/measurements_test.go +++ b/api/measurements_test.go @@ -11,7 +11,7 @@ func newMeasurement() *models.Measurement { measurement := models.NewMeasurement() measurement.Id = 1 measurement.StrainId = 2 - measurement.ObservationId = 3 + measurement.CharacteristicId = 3 measurement.TextMeasurementTypeId = models.NullInt64{sql.NullInt64{Int64: 4, Valid: false}} measurement.UnitTypeId = models.NullInt64{sql.NullInt64{Int64: 5, Valid: true}} return measurement diff --git a/api/observations.go b/api/observations.go deleted file mode 100644 index 23ecdce..0000000 --- a/api/observations.go +++ /dev/null @@ -1,92 +0,0 @@ -package api - -import ( - "encoding/json" - "net/http" - "strconv" - - "github.com/gorilla/mux" - "github.com/thermokarst/bactdb/models" -) - -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) -} - -func serveCreateObservation(w http.ResponseWriter, r *http.Request) error { - var observation models.Observation - err := json.NewDecoder(r.Body).Decode(&observation) - if err != nil { - return err - } - - created, err := store.Observations.Create(&observation) - if err != nil { - return err - } - if created { - w.WriteHeader(http.StatusCreated) - } - - return writeJSON(w, observation) -} - -func serveObservationList(w http.ResponseWriter, r *http.Request) error { - var opt models.ObservationListOptions - if err := schemaDecoder.Decode(&opt, r.URL.Query()); err != nil { - return err - } - - observations, err := store.Observations.List(&opt) - if err != nil { - return err - } - if observations == nil { - observations = []*models.Observation{} - } - - return writeJSON(w, observations) -} - -func serveUpdateObservation(w http.ResponseWriter, r *http.Request) error { - id, _ := strconv.ParseInt(mux.Vars(r)["Id"], 10, 0) - var observation models.Observation - err := json.NewDecoder(r.Body).Decode(&observation) - if err != nil { - return err - } - - updated, err := store.Observations.Update(id, &observation) - if err != nil { - return err - } - if updated { - w.WriteHeader(http.StatusOK) - } - - return writeJSON(w, observation) -} - -func serveDeleteObservation(w http.ResponseWriter, r *http.Request) error { - id, _ := strconv.ParseInt(mux.Vars(r)["Id"], 10, 0) - - deleted, err := store.Observations.Delete(id) - if err != nil { - return err - } - if deleted { - w.WriteHeader(http.StatusOK) - } - - return writeJSON(w, &models.Observation{}) -} diff --git a/api/observations_test.go b/api/observations_test.go deleted file mode 100644 index 49425d2..0000000 --- a/api/observations_test.go +++ /dev/null @@ -1,153 +0,0 @@ -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) - } -} - -func TestObservation_Create(t *testing.T) { - setup() - - want := newObservation() - - calledPost := false - store.Observations.(*models.MockObservationsService).Create_ = func(observation *models.Observation) (bool, error) { - if !normalizeDeepEqual(want, observation) { - t.Errorf("wanted request for observation %d but got %d", want, observation) - } - calledPost = true - return true, nil - } - - success, err := apiClient.Observations.Create(want) - if err != nil { - t.Fatal(err) - } - - if !calledPost { - t.Error("!calledPost") - } - if !success { - t.Error("!success") - } -} - -func TestObservation_List(t *testing.T) { - setup() - - want := []*models.Observation{newObservation()} - wantOpt := &models.ObservationListOptions{ListOptions: models.ListOptions{Page: 1, PerPage: 10}} - - calledList := false - store.Observations.(*models.MockObservationsService).List_ = func(opt *models.ObservationListOptions) ([]*models.Observation, error) { - if !normalizeDeepEqual(wantOpt, opt) { - t.Errorf("wanted options %d but got %d", wantOpt, opt) - } - calledList = true - return want, nil - } - - observations, err := apiClient.Observations.List(wantOpt) - if err != nil { - t.Fatal(err) - } - - if !calledList { - t.Error("!calledList") - } - - if !normalizeDeepEqual(&want, &observations) { - t.Errorf("got observations %+v but wanted observations %+v", observations, want) - } -} - -func TestObservation_Update(t *testing.T) { - setup() - - want := newObservation() - - calledPut := false - store.Observations.(*models.MockObservationsService).Update_ = func(id int64, observation *models.Observation) (bool, error) { - if id != want.Id { - t.Errorf("wanted request for observation %d but got %d", want.Id, id) - } - if !normalizeDeepEqual(want, observation) { - t.Errorf("wanted request for observation %d but got %d", want, observation) - } - calledPut = true - return true, nil - } - - success, err := apiClient.Observations.Update(want.Id, want) - if err != nil { - t.Fatal(err) - } - - if !calledPut { - t.Error("!calledPut") - } - if !success { - t.Error("!success") - } -} - -func TestObservation_Delete(t *testing.T) { - setup() - - want := newObservation() - - calledDelete := false - store.Observations.(*models.MockObservationsService).Delete_ = func(id int64) (bool, error) { - if id != want.Id { - t.Errorf("wanted request for observation %d but got %d", want.Id, id) - } - calledDelete = true - return true, nil - } - - success, err := apiClient.Observations.Delete(want.Id) - if err != nil { - t.Fatal(err) - } - - if !calledDelete { - t.Error("!calledDelete") - } - if !success { - t.Error("!success") - } -} diff --git a/datastore/characteristic_types_test.go b/datastore/characteristic_types_test.go index d5cb8ee..70d7617 100644 --- a/datastore/characteristic_types_test.go +++ b/datastore/characteristic_types_test.go @@ -19,7 +19,7 @@ func insertCharacteristicType(t *testing.T, tx *modl.Transaction) *models.Charac } func newCharacteristicType(t *testing.T, tx *modl.Transaction) *models.CharacteristicType { - return &models.CharacteristicType{CharacteristicTypeName: "Test Obs"} + return &models.CharacteristicType{CharacteristicTypeName: "Test Char"} } func TestCharacteristicTypesStore_Get_db(t *testing.T) { @@ -95,7 +95,7 @@ func TestCharacteristicTypesStore_Update_db(t *testing.T) { d := NewDatastore(tx) // Tweak it - characteristic_type.CharacteristicTypeName = "Updated Obs Type" + characteristic_type.CharacteristicTypeName = "Updated Char Type" updated, err := d.CharacteristicTypes.Update(characteristic_type.Id, characteristic_type) if err != nil { t.Fatal(err) diff --git a/datastore/characteristics.go b/datastore/characteristics.go new file mode 100644 index 0000000..da4fcda --- /dev/null +++ b/datastore/characteristics.go @@ -0,0 +1,87 @@ +package datastore + +import ( + "time" + + "github.com/thermokarst/bactdb/models" +) + +func init() { + DB.AddTableWithName(models.Characteristic{}, "characteristics").SetKeys(true, "Id") +} + +type characteristicsStore struct { + *Datastore +} + +func (s *characteristicsStore) Get(id int64) (*models.Characteristic, error) { + var characteristic []*models.Characteristic + if err := s.dbh.Select(&characteristic, `SELECT * FROM characteristics WHERE id=$1;`, id); err != nil { + return nil, err + } + if len(characteristic) == 0 { + return nil, models.ErrCharacteristicNotFound + } + return characteristic[0], nil +} + +func (s *characteristicsStore) Create(characteristic *models.Characteristic) (bool, error) { + currentTime := time.Now() + characteristic.CreatedAt = currentTime + characteristic.UpdatedAt = currentTime + if err := s.dbh.Insert(characteristic); err != nil { + return false, err + } + return true, nil +} + +func (s *characteristicsStore) List(opt *models.CharacteristicListOptions) ([]*models.Characteristic, error) { + if opt == nil { + opt = &models.CharacteristicListOptions{} + } + var characteristics []*models.Characteristic + err := s.dbh.Select(&characteristics, `SELECT * FROM characteristics LIMIT $1 OFFSET $2;`, opt.PerPageOrDefault(), opt.Offset()) + if err != nil { + return nil, err + } + return characteristics, nil +} + +func (s *characteristicsStore) Update(id int64, characteristic *models.Characteristic) (bool, error) { + _, err := s.Get(id) + if err != nil { + return false, err + } + + if id != characteristic.Id { + return false, models.ErrCharacteristicNotFound + } + + characteristic.UpdatedAt = time.Now() + changed, err := s.dbh.Update(characteristic) + if err != nil { + return false, err + } + + if changed == 0 { + return false, ErrNoRowsUpdated + } + + return true, nil +} + +func (s *characteristicsStore) Delete(id int64) (bool, error) { + characteristic, err := s.Get(id) + if err != nil { + return false, err + } + + deleted, err := s.dbh.Delete(characteristic) + if err != nil { + return false, err + } + if deleted == 0 { + return false, ErrNoRowsDeleted + } + return true, nil +} diff --git a/datastore/characteristics_test.go b/datastore/characteristics_test.go new file mode 100644 index 0000000..4d6a179 --- /dev/null +++ b/datastore/characteristics_test.go @@ -0,0 +1,129 @@ +package datastore + +import ( + "reflect" + "testing" + + "github.com/jmoiron/modl" + "github.com/thermokarst/bactdb/models" +) + +func insertCharacteristic(t *testing.T, tx *modl.Transaction) *models.Characteristic { + // clean up our target table + tx.Exec(`DELETE FROM characteristics;`) + characteristic := newCharacteristic(t, tx) + if err := tx.Insert(characteristic); err != nil { + t.Fatal(err) + } + return characteristic +} + +func newCharacteristic(t *testing.T, tx *modl.Transaction) *models.Characteristic { + // we want to create and insert an characteristic type record, too. + characteristic_type := insertCharacteristicType(t, tx) + return &models.Characteristic{CharacteristicName: "Test Characteristic", + CharacteristicTypeId: characteristic_type.Id} +} + +func TestCharacteristicsStore_Get_db(t *testing.T) { + tx, _ := DB.Begin() + defer tx.Rollback() + + want := insertCharacteristic(t, tx) + + d := NewDatastore(tx) + + characteristic, err := d.Characteristics.Get(want.Id) + if err != nil { + t.Fatal(err) + } + + normalizeTime(&want.CreatedAt, &want.UpdatedAt, &want.DeletedAt) + normalizeTime(&characteristic.CreatedAt, &characteristic.UpdatedAt, &characteristic.DeletedAt) + + if !reflect.DeepEqual(characteristic, want) { + t.Errorf("got characteristic %+v, want %+v", characteristic, want) + } +} + +func TestCharacteristicsStore_Create_db(t *testing.T) { + tx, _ := DB.Begin() + defer tx.Rollback() + + characteristic := newCharacteristic(t, tx) + + d := NewDatastore(tx) + + created, err := d.Characteristics.Create(characteristic) + if err != nil { + t.Fatal(err) + } + if !created { + t.Error("!created") + } + if characteristic.Id == 0 { + t.Error("want nonzero characteristic.Id after submitting") + } +} + +func TestCharacteristicsStore_List_db(t *testing.T) { + tx, _ := DB.Begin() + defer tx.Rollback() + + want_characteristic := insertCharacteristic(t, tx) + want := []*models.Characteristic{want_characteristic} + + d := NewDatastore(tx) + + characteristics, err := d.Characteristics.List(&models.CharacteristicListOptions{ListOptions: models.ListOptions{Page: 1, PerPage: 10}}) + if err != nil { + t.Fatal(err) + } + + for i := range want { + normalizeTime(&want[i].CreatedAt, &want[i].UpdatedAt, &want[i].DeletedAt) + normalizeTime(&characteristics[i].CreatedAt, &characteristics[i].UpdatedAt, &characteristics[i].DeletedAt) + } + if !reflect.DeepEqual(characteristics, want) { + t.Errorf("got characteristics %+v, want %+v", characteristics, want) + } +} + +func TestCharacteristicsStore_Update_db(t *testing.T) { + tx, _ := DB.Begin() + defer tx.Rollback() + + characteristic := insertCharacteristic(t, tx) + + d := NewDatastore(tx) + + // Tweak it + characteristic.CharacteristicName = "Updated Char" + updated, err := d.Characteristics.Update(characteristic.Id, characteristic) + if err != nil { + t.Fatal(err) + } + + if !updated { + t.Error("!updated") + } +} + +func TestCharacteristicsStore_Delete_db(t *testing.T) { + tx, _ := DB.Begin() + defer tx.Rollback() + + characteristic := insertCharacteristic(t, tx) + + d := NewDatastore(tx) + + // Delete it + deleted, err := d.Characteristics.Delete(characteristic.Id) + if err != nil { + t.Fatal(err) + } + + if !deleted { + t.Error("!delete") + } +} diff --git a/datastore/datastore.go b/datastore/datastore.go index 5a9e08c..d4589ad 100644 --- a/datastore/datastore.go +++ b/datastore/datastore.go @@ -13,8 +13,8 @@ type Datastore struct { Genera models.GeneraService Species models.SpeciesService Strains models.StrainsService - CharacteristicTypes models.CharacteristicTypesService - Observations models.ObservationsService + CharacteristicTypes models.CharacteristicTypesService + Characteristics models.CharacteristicsService TextMeasurementTypes models.TextMeasurementTypesService UnitTypes models.UnitTypesService Measurements models.MeasurementsService @@ -39,7 +39,7 @@ func NewDatastore(dbh modl.SqlExecutor) *Datastore { d.Species = &speciesStore{d} d.Strains = &strainsStore{d} d.CharacteristicTypes = &characteristicTypesStore{d} - d.Observations = &observationsStore{d} + d.Characteristics = &characteristicsStore{d} d.TextMeasurementTypes = &textMeasurementTypesStore{d} d.UnitTypes = &unitTypesStore{d} d.Measurements = &measurementsStore{d} @@ -52,8 +52,8 @@ func NewMockDatastore() *Datastore { Genera: &models.MockGeneraService{}, Species: &models.MockSpeciesService{}, Strains: &models.MockStrainsService{}, - CharacteristicTypes: &models.MockCharacteristicTypesService{}, - Observations: &models.MockObservationsService{}, + CharacteristicTypes: &models.MockCharacteristicTypesService{}, + Characteristics: &models.MockCharacteristicsService{}, TextMeasurementTypes: &models.MockTextMeasurementTypesService{}, UnitTypes: &models.MockUnitTypesService{}, Measurements: &models.MockMeasurementsService{}, diff --git a/datastore/measurements_test.go b/datastore/measurements_test.go index fd16e14..2b901f3 100644 --- a/datastore/measurements_test.go +++ b/datastore/measurements_test.go @@ -22,16 +22,16 @@ func insertMeasurement(t *testing.T, tx *modl.Transaction) *models.Measurement { func newMeasurement(t *testing.T, tx *modl.Transaction) *models.Measurement { // we have a few things to take care of first... strain := insertStrain(t, tx) - observation := insertObservation(t, tx) + characteristic := insertCharacteristic(t, tx) // we want to create and insert a unit type record, too. unit_type := insertUnitType(t, tx) return &models.Measurement{ - StrainId: strain.Id, - ObservationId: observation.Id, - NumValue: models.NullFloat64{sql.NullFloat64{Float64: 1.23, Valid: true}}, - UnitTypeId: models.NullInt64{sql.NullInt64{Int64: unit_type.Id, Valid: true}}, + StrainId: strain.Id, + CharacteristicId: characteristic.Id, + NumValue: models.NullFloat64{sql.NullFloat64{Float64: 1.23, Valid: true}}, + UnitTypeId: models.NullInt64{sql.NullInt64{Int64: unit_type.Id, Valid: true}}, } } diff --git a/datastore/migrations/00006_AddObservations_down.sql b/datastore/migrations/00006_AddCharacteristics_down.sql similarity index 53% rename from datastore/migrations/00006_AddObservations_down.sql rename to datastore/migrations/00006_AddCharacteristics_down.sql index 5b5ef85..458b79c 100644 --- a/datastore/migrations/00006_AddObservations_down.sql +++ b/datastore/migrations/00006_AddCharacteristics_down.sql @@ -1,5 +1,5 @@ -- bactdb -- Matthew R Dillon -DROP TABLE observations; +DROP TABLE characteristics; diff --git a/datastore/migrations/00006_AddObservations_up.sql b/datastore/migrations/00006_AddCharacteristics_up.sql similarity index 59% rename from datastore/migrations/00006_AddObservations_up.sql rename to datastore/migrations/00006_AddCharacteristics_up.sql index f5ad3f2..f49e88b 100644 --- a/datastore/migrations/00006_AddObservations_up.sql +++ b/datastore/migrations/00006_AddCharacteristics_up.sql @@ -1,18 +1,18 @@ -- bactdb -- Matthew R Dillon -CREATE TABLE observations ( +CREATE TABLE characteristics ( id BIGSERIAL NOT NULL, - observation_name CHARACTER VARYING(100) NOT NULL, + characteristic_name CHARACTER VARYING(100) NOT NULL, characteristic_type_id BIGINT NOT NULL, created_at TIMESTAMP WITH TIME ZONE NOT NULL, updated_at TIMESTAMP WITH TIME ZONE NOT NULL, deleted_at TIMESTAMP WITH TIME ZONE NULL, - CONSTRAINT observations_pkey PRIMARY KEY (id), + CONSTRAINT characteristics_pkey PRIMARY KEY (id), FOREIGN KEY (characteristic_type_id) REFERENCES characteristic_types(id) ); -CREATE INDEX characteristic_type_id_idx ON observations (characteristic_type_id); +CREATE INDEX characteristic_type_id_idx ON characteristics (characteristic_type_id); diff --git a/datastore/migrations/00010_AddMeasurements_up.sql b/datastore/migrations/00010_AddMeasurements_up.sql index 614e053..b4fff0c 100644 --- a/datastore/migrations/00010_AddMeasurements_up.sql +++ b/datastore/migrations/00010_AddMeasurements_up.sql @@ -4,7 +4,7 @@ CREATE TABLE measurements ( id BIGSERIAL NOT NULL, strain_id BIGINT NOT NULL, - observation_id BIGINT NOT NULL, + characteristic_id BIGINT NOT NULL, text_measurement_type_id BIGINT NULL, txt_value CHARACTER VARYING(255) NULL, num_value NUMERIC(8, 3) NULL, @@ -16,9 +16,9 @@ CREATE TABLE measurements ( created_at TIMESTAMP WITH TIME ZONE NOT NULL, updated_at TIMESTAMP WITH TIME ZONE NOT NULL, - CONSTRAINT strainsobsmeasurements_pkey PRIMARY KEY (id), + CONSTRAINT strainscharmeasurements_pkey PRIMARY KEY (id), FOREIGN KEY (strain_id) REFERENCES strains(id), - FOREIGN KEY (observation_id) REFERENCES observations(id), + FOREIGN KEY (characteristic_id) REFERENCES characteristics(id), FOREIGN KEY (text_measurement_type_id) REFERENCES text_measurement_types(id), FOREIGN KEY (unit_type_id) REFERENCES unit_types(id), FOREIGN KEY (test_method_id) REFERENCES test_methods(id), @@ -42,7 +42,7 @@ CREATE TABLE measurements ( CREATE INDEX strain_id_idx ON measurements (strain_id); -CREATE INDEX observation_id_idx ON measurements (observation_id); +CREATE INDEX characteristic_id_idx ON measurements (characteristic_id); CREATE INDEX text_measurement_type_id_idx ON measurements (text_measurement_type_id); diff --git a/datastore/observations.go b/datastore/observations.go deleted file mode 100644 index 06cc0a1..0000000 --- a/datastore/observations.go +++ /dev/null @@ -1,87 +0,0 @@ -package datastore - -import ( - "time" - - "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 -} - -func (s *observationsStore) Create(observation *models.Observation) (bool, error) { - currentTime := time.Now() - observation.CreatedAt = currentTime - observation.UpdatedAt = currentTime - if err := s.dbh.Insert(observation); err != nil { - return false, err - } - return true, nil -} - -func (s *observationsStore) List(opt *models.ObservationListOptions) ([]*models.Observation, error) { - if opt == nil { - opt = &models.ObservationListOptions{} - } - var observations []*models.Observation - err := s.dbh.Select(&observations, `SELECT * FROM observations LIMIT $1 OFFSET $2;`, opt.PerPageOrDefault(), opt.Offset()) - if err != nil { - return nil, err - } - return observations, nil -} - -func (s *observationsStore) Update(id int64, observation *models.Observation) (bool, error) { - _, err := s.Get(id) - if err != nil { - return false, err - } - - if id != observation.Id { - return false, models.ErrObservationNotFound - } - - observation.UpdatedAt = time.Now() - changed, err := s.dbh.Update(observation) - if err != nil { - return false, err - } - - if changed == 0 { - return false, ErrNoRowsUpdated - } - - return true, nil -} - -func (s *observationsStore) Delete(id int64) (bool, error) { - observation, err := s.Get(id) - if err != nil { - return false, err - } - - deleted, err := s.dbh.Delete(observation) - if err != nil { - return false, err - } - if deleted == 0 { - return false, ErrNoRowsDeleted - } - return true, nil -} diff --git a/datastore/observations_test.go b/datastore/observations_test.go deleted file mode 100644 index 7d3423d..0000000 --- a/datastore/observations_test.go +++ /dev/null @@ -1,129 +0,0 @@ -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 characteristic type record, too. - characteristic_type := insertCharacteristicType(t, tx) - return &models.Observation{ObservationName: "Test Observation", - CharacteristicTypeId: characteristic_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) - } -} - -func TestObservationsStore_Create_db(t *testing.T) { - tx, _ := DB.Begin() - defer tx.Rollback() - - observation := newObservation(t, tx) - - d := NewDatastore(tx) - - created, err := d.Observations.Create(observation) - if err != nil { - t.Fatal(err) - } - if !created { - t.Error("!created") - } - if observation.Id == 0 { - t.Error("want nonzero observation.Id after submitting") - } -} - -func TestObservationsStore_List_db(t *testing.T) { - tx, _ := DB.Begin() - defer tx.Rollback() - - want_observation := insertObservation(t, tx) - want := []*models.Observation{want_observation} - - d := NewDatastore(tx) - - observations, err := d.Observations.List(&models.ObservationListOptions{ListOptions: models.ListOptions{Page: 1, PerPage: 10}}) - if err != nil { - t.Fatal(err) - } - - for i := range want { - normalizeTime(&want[i].CreatedAt, &want[i].UpdatedAt, &want[i].DeletedAt) - normalizeTime(&observations[i].CreatedAt, &observations[i].UpdatedAt, &observations[i].DeletedAt) - } - if !reflect.DeepEqual(observations, want) { - t.Errorf("got observations %+v, want %+v", observations, want) - } -} - -func TestObservationsStore_Update_db(t *testing.T) { - tx, _ := DB.Begin() - defer tx.Rollback() - - observation := insertObservation(t, tx) - - d := NewDatastore(tx) - - // Tweak it - observation.ObservationName = "Updated Obs" - updated, err := d.Observations.Update(observation.Id, observation) - if err != nil { - t.Fatal(err) - } - - if !updated { - t.Error("!updated") - } -} - -func TestObservationsStore_Delete_db(t *testing.T) { - tx, _ := DB.Begin() - defer tx.Rollback() - - observation := insertObservation(t, tx) - - d := NewDatastore(tx) - - // Delete it - deleted, err := d.Observations.Delete(observation.Id) - if err != nil { - t.Fatal(err) - } - - if !deleted { - t.Error("!delete") - } -} diff --git a/models/characteristic_types.go b/models/characteristic_types.go index d2f22a8..ede904b 100644 --- a/models/characteristic_types.go +++ b/models/characteristic_types.go @@ -20,7 +20,7 @@ type CharacteristicType struct { func NewCharacteristicType() *CharacteristicType { return &CharacteristicType{ - CharacteristicTypeName: "Test Obs Type", + CharacteristicTypeName: "Test Char Type", } } diff --git a/models/characteristic_types_test.go b/models/characteristic_types_test.go index 2bd382d..20493e9 100644 --- a/models/characteristic_types_test.go +++ b/models/characteristic_types_test.go @@ -54,7 +54,7 @@ func TestCharacteristicTypeService_Create(t *testing.T) { mux.HandleFunc(urlPath(t, router.CreateCharacteristicType, nil), func(w http.ResponseWriter, r *http.Request) { called = true testMethod(t, r, "POST") - testBody(t, r, `{"id":1,"characteristicTypeName":"Test Obs Type","createdAt":"0001-01-01T00:00:00Z","updatedAt":"0001-01-01T00:00:00Z","deletedAt":null}`+"\n") + testBody(t, r, `{"id":1,"characteristicTypeName":"Test Char Type","createdAt":"0001-01-01T00:00:00Z","updatedAt":"0001-01-01T00:00:00Z","deletedAt":null}`+"\n") w.WriteHeader(http.StatusCreated) writeJSON(w, want) @@ -123,13 +123,13 @@ func TestCharacteristicTypeService_Update(t *testing.T) { mux.HandleFunc(urlPath(t, router.UpdateCharacteristicType, map[string]string{"Id": "1"}), func(w http.ResponseWriter, r *http.Request) { called = true testMethod(t, r, "PUT") - testBody(t, r, `{"id":1,"characteristicTypeName":"Test Obs Type Updated","createdAt":"0001-01-01T00:00:00Z","updatedAt":"0001-01-01T00:00:00Z","deletedAt":null}`+"\n") + testBody(t, r, `{"id":1,"characteristicTypeName":"Test Char Type Updated","createdAt":"0001-01-01T00:00:00Z","updatedAt":"0001-01-01T00:00:00Z","deletedAt":null}`+"\n") w.WriteHeader(http.StatusOK) writeJSON(w, want) }) characteristic_type := newCharacteristicType() - characteristic_type.CharacteristicTypeName = "Test Obs Type Updated" + characteristic_type.CharacteristicTypeName = "Test Char Type Updated" updated, err := client.CharacteristicTypes.Update(characteristic_type.Id, characteristic_type) if err != nil { t.Errorf("CharacteristicTypes.Update returned error: %v", err) diff --git a/models/characteristics.go b/models/characteristics.go new file mode 100644 index 0000000..545933d --- /dev/null +++ b/models/characteristics.go @@ -0,0 +1,204 @@ +package models + +import ( + "errors" + "net/http" + "strconv" + "time" + + "github.com/thermokarst/bactdb/router" +) + +// A Characteristic is a lookup type +type Characteristic struct { + Id int64 `json:"id,omitempty"` + CharacteristicName string `db:"characteristic_name" json:"characteristicName"` + CharacteristicTypeId int64 `db:"characteristic_type_id" json:"characteristicTypeId"` + CreatedAt time.Time `db:"created_at" json:"createdAt"` + UpdatedAt time.Time `db:"updated_at" json:"updatedAt"` + DeletedAt NullTime `db:"deleted_at" json:"deletedAt"` +} + +func NewCharacteristic() *Characteristic { + return &Characteristic{ + CharacteristicName: "Test Characteristic", + } +} + +type CharacteristicsService interface { + // Get an characteristic + Get(id int64) (*Characteristic, error) + + // List all characteristics + List(opt *CharacteristicListOptions) ([]*Characteristic, error) + + // Create an characteristic + Create(characteristic *Characteristic) (bool, error) + + // Update an characteristic + Update(id int64, Characteristic *Characteristic) (updated bool, err error) + + // Delete an characteristic + Delete(id int64) (deleted bool, err error) +} + +var ( + ErrCharacteristicNotFound = errors.New("characteristic not found") +) + +type characteristicsService struct { + client *Client +} + +func (s *characteristicsService) Get(id int64) (*Characteristic, error) { + strId := strconv.FormatInt(id, 10) + + url, err := s.client.url(router.Characteristic, 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 characteristic *Characteristic + _, err = s.client.Do(req, &characteristic) + if err != nil { + return nil, err + } + + return characteristic, nil +} + +func (s *characteristicsService) Create(characteristic *Characteristic) (bool, error) { + url, err := s.client.url(router.CreateCharacteristic, nil, nil) + if err != nil { + return false, err + } + + req, err := s.client.NewRequest("POST", url.String(), characteristic) + if err != nil { + return false, err + } + + resp, err := s.client.Do(req, &characteristic) + if err != nil { + return false, err + } + + return resp.StatusCode == http.StatusCreated, nil +} + +type CharacteristicListOptions struct { + ListOptions +} + +func (s *characteristicsService) List(opt *CharacteristicListOptions) ([]*Characteristic, error) { + url, err := s.client.url(router.Characteristics, nil, opt) + if err != nil { + return nil, err + } + + req, err := s.client.NewRequest("GET", url.String(), nil) + if err != nil { + return nil, err + } + + var characteristics []*Characteristic + _, err = s.client.Do(req, &characteristics) + if err != nil { + return nil, err + } + + return characteristics, nil +} + +func (s *characteristicsService) Update(id int64, characteristic *Characteristic) (bool, error) { + strId := strconv.FormatInt(id, 10) + + url, err := s.client.url(router.UpdateCharacteristic, map[string]string{"Id": strId}, nil) + if err != nil { + return false, err + } + + req, err := s.client.NewRequest("PUT", url.String(), characteristic) + if err != nil { + return false, err + } + + resp, err := s.client.Do(req, &characteristic) + if err != nil { + return false, err + } + + return resp.StatusCode == http.StatusOK, nil +} + +func (s *characteristicsService) Delete(id int64) (bool, error) { + strId := strconv.FormatInt(id, 10) + + url, err := s.client.url(router.DeleteCharacteristic, 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 characteristic *Characteristic + resp, err := s.client.Do(req, &characteristic) + if err != nil { + return false, err + } + + return resp.StatusCode == http.StatusOK, nil +} + +type MockCharacteristicsService struct { + Get_ func(id int64) (*Characteristic, error) + List_ func(opt *CharacteristicListOptions) ([]*Characteristic, error) + Create_ func(characteristic *Characteristic) (bool, error) + Update_ func(id int64, characteristic *Characteristic) (bool, error) + Delete_ func(id int64) (bool, error) +} + +var _ CharacteristicsService = &MockCharacteristicsService{} + +func (s *MockCharacteristicsService) Get(id int64) (*Characteristic, error) { + if s.Get_ == nil { + return nil, nil + } + return s.Get_(id) +} + +func (s *MockCharacteristicsService) Create(characteristic *Characteristic) (bool, error) { + if s.Create_ == nil { + return false, nil + } + return s.Create_(characteristic) +} + +func (s *MockCharacteristicsService) List(opt *CharacteristicListOptions) ([]*Characteristic, error) { + if s.List_ == nil { + return nil, nil + } + return s.List_(opt) +} + +func (s *MockCharacteristicsService) Update(id int64, characteristic *Characteristic) (bool, error) { + if s.Update_ == nil { + return false, nil + } + return s.Update_(id, characteristic) +} + +func (s *MockCharacteristicsService) Delete(id int64) (bool, error) { + if s.Delete_ == nil { + return false, nil + } + return s.Delete_(id) +} diff --git a/models/characteristics_test.go b/models/characteristics_test.go new file mode 100644 index 0000000..0ed8744 --- /dev/null +++ b/models/characteristics_test.go @@ -0,0 +1,174 @@ +package models + +import ( + "net/http" + "reflect" + "testing" + + "github.com/thermokarst/bactdb/router" +) + +func newCharacteristic() *Characteristic { + characteristic := NewCharacteristic() + characteristic.Id = 1 + return characteristic +} + +func TestCharacteristicService_Get(t *testing.T) { + setup() + defer teardown() + + want := newCharacteristic() + + var called bool + mux.HandleFunc(urlPath(t, router.Characteristic, map[string]string{"Id": "1"}), func(w http.ResponseWriter, r *http.Request) { + called = true + testMethod(t, r, "GET") + + writeJSON(w, want) + }) + + characteristic, err := client.Characteristics.Get(want.Id) + if err != nil { + t.Errorf("Characteristics.Get returned error: %v", err) + } + + if !called { + t.Fatal("!called") + } + + normalizeTime(&want.CreatedAt, &want.UpdatedAt, &want.DeletedAt) + + if !reflect.DeepEqual(characteristic, want) { + t.Errorf("Characteristics.Get return %+v, want %+v", characteristic, want) + } +} + +func TestCharacteristicService_Create(t *testing.T) { + setup() + defer teardown() + + want := newCharacteristic() + + var called bool + mux.HandleFunc(urlPath(t, router.CreateCharacteristic, nil), func(w http.ResponseWriter, r *http.Request) { + called = true + testMethod(t, r, "POST") + testBody(t, r, `{"id":1,"characteristicName":"Test Characteristic","characteristicTypeId":0,"createdAt":"0001-01-01T00:00:00Z","updatedAt":"0001-01-01T00:00:00Z","deletedAt":null}`+"\n") + + w.WriteHeader(http.StatusCreated) + writeJSON(w, want) + }) + + characteristic := newCharacteristic() + created, err := client.Characteristics.Create(characteristic) + if err != nil { + t.Errorf("Characteristics.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(characteristic, want) { + t.Errorf("Characteristics.Create returned %+v, want %+v", characteristic, want) + } +} + +func TestCharacteristicService_List(t *testing.T) { + setup() + defer teardown() + + want := []*Characteristic{newCharacteristic()} + + var called bool + mux.HandleFunc(urlPath(t, router.Characteristics, nil), func(w http.ResponseWriter, r *http.Request) { + called = true + testMethod(t, r, "GET") + testFormValues(t, r, values{}) + + writeJSON(w, want) + }) + + characteristics, err := client.Characteristics.List(nil) + if err != nil { + t.Errorf("Characteristics.List returned error: %v", err) + } + + if !called { + t.Fatal("!called") + } + + for _, u := range want { + normalizeTime(&u.CreatedAt, &u.UpdatedAt, &u.DeletedAt) + } + + if !reflect.DeepEqual(characteristics, want) { + t.Errorf("Characteristics.List return %+v, want %+v", characteristics, want) + } +} + +func TestCharacteristicService_Update(t *testing.T) { + setup() + defer teardown() + + want := newCharacteristic() + + var called bool + mux.HandleFunc(urlPath(t, router.UpdateCharacteristic, map[string]string{"Id": "1"}), func(w http.ResponseWriter, r *http.Request) { + called = true + testMethod(t, r, "PUT") + testBody(t, r, `{"id":1,"characteristicName":"Test Char Updated","characteristicTypeId":0,"createdAt":"0001-01-01T00:00:00Z","updatedAt":"0001-01-01T00:00:00Z","deletedAt":null}`+"\n") + w.WriteHeader(http.StatusOK) + writeJSON(w, want) + }) + + characteristic := newCharacteristic() + characteristic.CharacteristicName = "Test Char Updated" + updated, err := client.Characteristics.Update(characteristic.Id, characteristic) + if err != nil { + t.Errorf("Characteristics.Update returned error: %v", err) + } + + if !updated { + t.Error("!updated") + } + + if !called { + t.Fatal("!called") + } +} + +func TestCharacteristicService_Delete(t *testing.T) { + setup() + defer teardown() + + want := newCharacteristic() + + var called bool + mux.HandleFunc(urlPath(t, router.DeleteCharacteristic, 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.Characteristics.Delete(want.Id) + if err != nil { + t.Errorf("Characteristics.Delete returned error: %v", err) + } + + if !deleted { + t.Error("!deleted") + } + + if !called { + t.Fatal("!called") + } +} diff --git a/models/client.go b/models/client.go index cd69525..234f68d 100644 --- a/models/client.go +++ b/models/client.go @@ -20,8 +20,8 @@ type Client struct { Genera GeneraService Species SpeciesService Strains StrainsService - CharacteristicTypes CharacteristicTypesService - Observations ObservationsService + CharacteristicTypes CharacteristicTypesService + Characteristics CharacteristicsService TextMeasurementTypes TextMeasurementTypesService UnitTypes UnitTypesService Measurements MeasurementsService @@ -57,7 +57,7 @@ func NewClient(httpClient *http.Client) *Client { c.Species = &speciesService{c} c.Strains = &strainsService{c} c.CharacteristicTypes = &characteristicTypesService{c} - c.Observations = &observationsService{c} + c.Characteristics = &characteristicsService{c} c.TextMeasurementTypes = &textMeasurementTypesService{c} c.UnitTypes = &unitTypesService{c} c.Measurements = &measurementsService{c} diff --git a/models/measurements.go b/models/measurements.go index f643e2f..0de3160 100644 --- a/models/measurements.go +++ b/models/measurements.go @@ -13,11 +13,11 @@ import ( // A Measurement is the main data type for this application // There are two types of supported measurements: text & numerical. The table // has a constraint that will allow one or the other for a particular -// combination of strain & observation, but not both. +// combination of strain & characteristic, but not both. type Measurement struct { Id int64 `json:"id,omitempty"` StrainId int64 `db:"strain_id" json:"strainId"` - ObservationId int64 `db:"observation_id" json:"observationId"` + CharacteristicId int64 `db:"characteristic_id" json:"characteristicId"` TextMeasurementTypeId NullInt64 `db:"text_measurement_type_id" json:"textMeasurementTypeId"` TxtValue NullString `db:"txt_value" json:"txtValue"` NumValue NullFloat64 `db:"num_value" json:"numValue"` diff --git a/models/measurements_test.go b/models/measurements_test.go index 55785b1..6e59894 100644 --- a/models/measurements_test.go +++ b/models/measurements_test.go @@ -13,7 +13,7 @@ func newMeasurement() *Measurement { measurement := NewMeasurement() measurement.Id = 1 measurement.StrainId = 1 - measurement.ObservationId = 1 + measurement.CharacteristicId = 1 measurement.UnitTypeId = NullInt64{sql.NullInt64{Int64: 1, Valid: true}} return measurement } @@ -58,7 +58,7 @@ func TestMeasurementService_Create(t *testing.T) { mux.HandleFunc(urlPath(t, router.CreateMeasurement, nil), func(w http.ResponseWriter, r *http.Request) { called = true testMethod(t, r, "POST") - testBody(t, r, `{"id":1,"strainId":1,"observationId":1,"textMeasurementTypeId":null,"txtValue":null,"numValue":1.23,"confidenceInterval":null,"unitTypeId":1,"notes":null,"testMethodId":null,"createdAt":"0001-01-01T00:00:00Z","updatedAt":"0001-01-01T00:00:00Z"}`+"\n") + testBody(t, r, `{"id":1,"strainId":1,"characteristicId":1,"textMeasurementTypeId":null,"txtValue":null,"numValue":1.23,"confidenceInterval":null,"unitTypeId":1,"notes":null,"testMethodId":null,"createdAt":"0001-01-01T00:00:00Z","updatedAt":"0001-01-01T00:00:00Z"}`+"\n") w.WriteHeader(http.StatusCreated) writeJSON(w, want) @@ -127,7 +127,7 @@ func TestMeasurementService_Update(t *testing.T) { mux.HandleFunc(urlPath(t, router.UpdateMeasurement, map[string]string{"Id": "1"}), func(w http.ResponseWriter, r *http.Request) { called = true testMethod(t, r, "PUT") - testBody(t, r, `{"id":1,"strainId":1,"observationId":1,"textMeasurementTypeId":null,"txtValue":null,"numValue":4.56,"confidenceInterval":null,"unitTypeId":1,"notes":null,"testMethodId":null,"createdAt":"0001-01-01T00:00:00Z","updatedAt":"0001-01-01T00:00:00Z"}`+"\n") + testBody(t, r, `{"id":1,"strainId":1,"characteristicId":1,"textMeasurementTypeId":null,"txtValue":null,"numValue":4.56,"confidenceInterval":null,"unitTypeId":1,"notes":null,"testMethodId":null,"createdAt":"0001-01-01T00:00:00Z","updatedAt":"0001-01-01T00:00:00Z"}`+"\n") w.WriteHeader(http.StatusOK) writeJSON(w, want) }) diff --git a/models/observations.go b/models/observations.go deleted file mode 100644 index 890bdca..0000000 --- a/models/observations.go +++ /dev/null @@ -1,204 +0,0 @@ -package models - -import ( - "errors" - "net/http" - "strconv" - "time" - - "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:"observationName"` - CharacteristicTypeId int64 `db:"characteristic_type_id" json:"characteristicTypeId"` - CreatedAt time.Time `db:"created_at" json:"createdAt"` - UpdatedAt time.Time `db:"updated_at" json:"updatedAt"` - DeletedAt NullTime `db:"deleted_at" json:"deletedAt"` -} - -func NewObservation() *Observation { - return &Observation{ - ObservationName: "Test Observation", - } -} - -type ObservationsService interface { - // Get an observation - Get(id int64) (*Observation, error) - - // List all observations - List(opt *ObservationListOptions) ([]*Observation, error) - - // Create an observation - Create(observation *Observation) (bool, error) - - // Update an observation - Update(id int64, Observation *Observation) (updated bool, err error) - - // Delete an observation - Delete(id int64) (deleted bool, err 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 -} - -func (s *observationsService) Create(observation *Observation) (bool, error) { - url, err := s.client.url(router.CreateObservation, nil, nil) - if err != nil { - return false, err - } - - req, err := s.client.NewRequest("POST", url.String(), observation) - if err != nil { - return false, err - } - - resp, err := s.client.Do(req, &observation) - if err != nil { - return false, err - } - - return resp.StatusCode == http.StatusCreated, nil -} - -type ObservationListOptions struct { - ListOptions -} - -func (s *observationsService) List(opt *ObservationListOptions) ([]*Observation, error) { - url, err := s.client.url(router.Observations, nil, opt) - if err != nil { - return nil, err - } - - req, err := s.client.NewRequest("GET", url.String(), nil) - if err != nil { - return nil, err - } - - var observations []*Observation - _, err = s.client.Do(req, &observations) - if err != nil { - return nil, err - } - - return observations, nil -} - -func (s *observationsService) Update(id int64, observation *Observation) (bool, error) { - strId := strconv.FormatInt(id, 10) - - url, err := s.client.url(router.UpdateObservation, map[string]string{"Id": strId}, nil) - if err != nil { - return false, err - } - - req, err := s.client.NewRequest("PUT", url.String(), observation) - if err != nil { - return false, err - } - - resp, err := s.client.Do(req, &observation) - if err != nil { - return false, err - } - - return resp.StatusCode == http.StatusOK, nil -} - -func (s *observationsService) Delete(id int64) (bool, error) { - strId := strconv.FormatInt(id, 10) - - url, err := s.client.url(router.DeleteObservation, 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 observation *Observation - resp, err := s.client.Do(req, &observation) - if err != nil { - return false, err - } - - return resp.StatusCode == http.StatusOK, nil -} - -type MockObservationsService struct { - Get_ func(id int64) (*Observation, error) - List_ func(opt *ObservationListOptions) ([]*Observation, error) - Create_ func(observation *Observation) (bool, error) - Update_ func(id int64, observation *Observation) (bool, error) - Delete_ func(id int64) (bool, error) -} - -var _ ObservationsService = &MockObservationsService{} - -func (s *MockObservationsService) Get(id int64) (*Observation, error) { - if s.Get_ == nil { - return nil, nil - } - return s.Get_(id) -} - -func (s *MockObservationsService) Create(observation *Observation) (bool, error) { - if s.Create_ == nil { - return false, nil - } - return s.Create_(observation) -} - -func (s *MockObservationsService) List(opt *ObservationListOptions) ([]*Observation, error) { - if s.List_ == nil { - return nil, nil - } - return s.List_(opt) -} - -func (s *MockObservationsService) Update(id int64, observation *Observation) (bool, error) { - if s.Update_ == nil { - return false, nil - } - return s.Update_(id, observation) -} - -func (s *MockObservationsService) Delete(id int64) (bool, error) { - if s.Delete_ == nil { - return false, nil - } - return s.Delete_(id) -} diff --git a/models/observations_test.go b/models/observations_test.go deleted file mode 100644 index fde4311..0000000 --- a/models/observations_test.go +++ /dev/null @@ -1,174 +0,0 @@ -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) - } -} - -func TestObservationService_Create(t *testing.T) { - setup() - defer teardown() - - want := newObservation() - - var called bool - mux.HandleFunc(urlPath(t, router.CreateObservation, nil), func(w http.ResponseWriter, r *http.Request) { - called = true - testMethod(t, r, "POST") - testBody(t, r, `{"id":1,"observationName":"Test Observation","characteristicTypeId":0,"createdAt":"0001-01-01T00:00:00Z","updatedAt":"0001-01-01T00:00:00Z","deletedAt":null}`+"\n") - - w.WriteHeader(http.StatusCreated) - writeJSON(w, want) - }) - - observation := newObservation() - created, err := client.Observations.Create(observation) - if err != nil { - t.Errorf("Observations.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(observation, want) { - t.Errorf("Observations.Create returned %+v, want %+v", observation, want) - } -} - -func TestObservationService_List(t *testing.T) { - setup() - defer teardown() - - want := []*Observation{newObservation()} - - var called bool - mux.HandleFunc(urlPath(t, router.Observations, nil), func(w http.ResponseWriter, r *http.Request) { - called = true - testMethod(t, r, "GET") - testFormValues(t, r, values{}) - - writeJSON(w, want) - }) - - observations, err := client.Observations.List(nil) - if err != nil { - t.Errorf("Observations.List returned error: %v", err) - } - - if !called { - t.Fatal("!called") - } - - for _, u := range want { - normalizeTime(&u.CreatedAt, &u.UpdatedAt, &u.DeletedAt) - } - - if !reflect.DeepEqual(observations, want) { - t.Errorf("Observations.List return %+v, want %+v", observations, want) - } -} - -func TestObservationService_Update(t *testing.T) { - setup() - defer teardown() - - want := newObservation() - - var called bool - mux.HandleFunc(urlPath(t, router.UpdateObservation, map[string]string{"Id": "1"}), func(w http.ResponseWriter, r *http.Request) { - called = true - testMethod(t, r, "PUT") - testBody(t, r, `{"id":1,"observationName":"Test Obs Updated","characteristicTypeId":0,"createdAt":"0001-01-01T00:00:00Z","updatedAt":"0001-01-01T00:00:00Z","deletedAt":null}`+"\n") - w.WriteHeader(http.StatusOK) - writeJSON(w, want) - }) - - observation := newObservation() - observation.ObservationName = "Test Obs Updated" - updated, err := client.Observations.Update(observation.Id, observation) - if err != nil { - t.Errorf("Observations.Update returned error: %v", err) - } - - if !updated { - t.Error("!updated") - } - - if !called { - t.Fatal("!called") - } -} - -func TestObservationService_Delete(t *testing.T) { - setup() - defer teardown() - - want := newObservation() - - var called bool - mux.HandleFunc(urlPath(t, router.DeleteObservation, 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.Observations.Delete(want.Id) - if err != nil { - t.Errorf("Observations.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 f9e414b..4ca79e4 100644 --- a/router/api.go +++ b/router/api.go @@ -38,12 +38,12 @@ func API() *mux.Router { m.Path("/characteristic_types/{Id:.+}").Methods("PUT").Name(UpdateCharacteristicType) m.Path("/characteristic_types/{Id:.+}").Methods("DELETE").Name(DeleteCharacteristicType) - // Observations - m.Path("/observations").Methods("GET").Name(Observations) - m.Path("/observations").Methods("POST").Name(CreateObservation) - m.Path("/observations/{Id:.+}").Methods("GET").Name(Observation) - m.Path("/observations/{Id:.+}").Methods("PUT").Name(UpdateObservation) - m.Path("/observations/{Id:.+}").Methods("DELETE").Name(DeleteObservation) + // Characteristics + m.Path("/characteristics").Methods("GET").Name(Characteristics) + m.Path("/characteristics").Methods("POST").Name(CreateCharacteristic) + m.Path("/characteristics/{Id:.+}").Methods("GET").Name(Characteristic) + m.Path("/characteristics/{Id:.+}").Methods("PUT").Name(UpdateCharacteristic) + m.Path("/characteristics/{Id:.+}").Methods("DELETE").Name(DeleteCharacteristic) // TextMeasurementTypes m.Path("/text_measurement_types/").Methods("GET").Name(TextMeasurementTypes) diff --git a/router/routes.go b/router/routes.go index 1b4b81f..a8e92b9 100644 --- a/router/routes.go +++ b/router/routes.go @@ -29,11 +29,11 @@ const ( UpdateCharacteristicType = "characteristic_type:update" DeleteCharacteristicType = "characteristic_type:delete" - Observation = "observation:get" - CreateObservation = "observation:create" - Observations = "observation:list" - UpdateObservation = "observation:update" - DeleteObservation = "observation:delete" + Characteristic = "characteristic:get" + CreateCharacteristic = "characteristic:create" + Characteristics = "characteristic:list" + UpdateCharacteristic = "characteristic:update" + DeleteCharacteristic = "characteristic:delete" TextMeasurementType = "text_measurement_type:get" CreateTextMeasurementType = "text_measurement_type:create"