Renaming observations to characteristics

This commit is contained in:
Matthew Dillon 2014-12-12 10:34:56 -09:00
parent c1323f9c1f
commit 950b15a117
27 changed files with 889 additions and 889 deletions

92
api/characteristics.go Normal file
View file

@ -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{})
}

153
api/characteristics_test.go Normal file
View file

@ -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")
}
}

View file

@ -45,11 +45,11 @@ func Handler() *mux.Router {
m.Get(router.UpdateCharacteristicType).Handler(handler(serveUpdateCharacteristicType)) m.Get(router.UpdateCharacteristicType).Handler(handler(serveUpdateCharacteristicType))
m.Get(router.DeleteCharacteristicType).Handler(handler(serveDeleteCharacteristicType)) m.Get(router.DeleteCharacteristicType).Handler(handler(serveDeleteCharacteristicType))
m.Get(router.Observation).Handler(handler(serveObservation)) m.Get(router.Characteristic).Handler(handler(serveCharacteristic))
m.Get(router.CreateObservation).Handler(handler(serveCreateObservation)) m.Get(router.CreateCharacteristic).Handler(handler(serveCreateCharacteristic))
m.Get(router.Observations).Handler(handler(serveObservationList)) m.Get(router.Characteristics).Handler(handler(serveCharacteristicList))
m.Get(router.UpdateObservation).Handler(handler(serveUpdateObservation)) m.Get(router.UpdateCharacteristic).Handler(handler(serveUpdateCharacteristic))
m.Get(router.DeleteObservation).Handler(handler(serveDeleteObservation)) m.Get(router.DeleteCharacteristic).Handler(handler(serveDeleteCharacteristic))
m.Get(router.TextMeasurementType).Handler(handler(serveTextMeasurementType)) m.Get(router.TextMeasurementType).Handler(handler(serveTextMeasurementType))
m.Get(router.CreateTextMeasurementType).Handler(handler(serveCreateTextMeasurementType)) m.Get(router.CreateTextMeasurementType).Handler(handler(serveCreateTextMeasurementType))

View file

@ -11,7 +11,7 @@ func newMeasurement() *models.Measurement {
measurement := models.NewMeasurement() measurement := models.NewMeasurement()
measurement.Id = 1 measurement.Id = 1
measurement.StrainId = 2 measurement.StrainId = 2
measurement.ObservationId = 3 measurement.CharacteristicId = 3
measurement.TextMeasurementTypeId = models.NullInt64{sql.NullInt64{Int64: 4, Valid: false}} measurement.TextMeasurementTypeId = models.NullInt64{sql.NullInt64{Int64: 4, Valid: false}}
measurement.UnitTypeId = models.NullInt64{sql.NullInt64{Int64: 5, Valid: true}} measurement.UnitTypeId = models.NullInt64{sql.NullInt64{Int64: 5, Valid: true}}
return measurement return measurement

View file

@ -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{})
}

View file

@ -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")
}
}

View file

@ -19,7 +19,7 @@ func insertCharacteristicType(t *testing.T, tx *modl.Transaction) *models.Charac
} }
func newCharacteristicType(t *testing.T, tx *modl.Transaction) *models.CharacteristicType { 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) { func TestCharacteristicTypesStore_Get_db(t *testing.T) {
@ -95,7 +95,7 @@ func TestCharacteristicTypesStore_Update_db(t *testing.T) {
d := NewDatastore(tx) d := NewDatastore(tx)
// Tweak it // Tweak it
characteristic_type.CharacteristicTypeName = "Updated Obs Type" characteristic_type.CharacteristicTypeName = "Updated Char Type"
updated, err := d.CharacteristicTypes.Update(characteristic_type.Id, characteristic_type) updated, err := d.CharacteristicTypes.Update(characteristic_type.Id, characteristic_type)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)

View file

@ -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
}

View file

@ -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")
}
}

View file

@ -13,8 +13,8 @@ type Datastore struct {
Genera models.GeneraService Genera models.GeneraService
Species models.SpeciesService Species models.SpeciesService
Strains models.StrainsService Strains models.StrainsService
CharacteristicTypes models.CharacteristicTypesService CharacteristicTypes models.CharacteristicTypesService
Observations models.ObservationsService Characteristics models.CharacteristicsService
TextMeasurementTypes models.TextMeasurementTypesService TextMeasurementTypes models.TextMeasurementTypesService
UnitTypes models.UnitTypesService UnitTypes models.UnitTypesService
Measurements models.MeasurementsService Measurements models.MeasurementsService
@ -39,7 +39,7 @@ func NewDatastore(dbh modl.SqlExecutor) *Datastore {
d.Species = &speciesStore{d} d.Species = &speciesStore{d}
d.Strains = &strainsStore{d} d.Strains = &strainsStore{d}
d.CharacteristicTypes = &characteristicTypesStore{d} d.CharacteristicTypes = &characteristicTypesStore{d}
d.Observations = &observationsStore{d} d.Characteristics = &characteristicsStore{d}
d.TextMeasurementTypes = &textMeasurementTypesStore{d} d.TextMeasurementTypes = &textMeasurementTypesStore{d}
d.UnitTypes = &unitTypesStore{d} d.UnitTypes = &unitTypesStore{d}
d.Measurements = &measurementsStore{d} d.Measurements = &measurementsStore{d}
@ -52,8 +52,8 @@ func NewMockDatastore() *Datastore {
Genera: &models.MockGeneraService{}, Genera: &models.MockGeneraService{},
Species: &models.MockSpeciesService{}, Species: &models.MockSpeciesService{},
Strains: &models.MockStrainsService{}, Strains: &models.MockStrainsService{},
CharacteristicTypes: &models.MockCharacteristicTypesService{}, CharacteristicTypes: &models.MockCharacteristicTypesService{},
Observations: &models.MockObservationsService{}, Characteristics: &models.MockCharacteristicsService{},
TextMeasurementTypes: &models.MockTextMeasurementTypesService{}, TextMeasurementTypes: &models.MockTextMeasurementTypesService{},
UnitTypes: &models.MockUnitTypesService{}, UnitTypes: &models.MockUnitTypesService{},
Measurements: &models.MockMeasurementsService{}, Measurements: &models.MockMeasurementsService{},

View file

@ -22,16 +22,16 @@ func insertMeasurement(t *testing.T, tx *modl.Transaction) *models.Measurement {
func newMeasurement(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... // we have a few things to take care of first...
strain := insertStrain(t, tx) strain := insertStrain(t, tx)
observation := insertObservation(t, tx) characteristic := insertCharacteristic(t, tx)
// we want to create and insert a unit type record, too. // we want to create and insert a unit type record, too.
unit_type := insertUnitType(t, tx) unit_type := insertUnitType(t, tx)
return &models.Measurement{ return &models.Measurement{
StrainId: strain.Id, StrainId: strain.Id,
ObservationId: observation.Id, CharacteristicId: characteristic.Id,
NumValue: models.NullFloat64{sql.NullFloat64{Float64: 1.23, Valid: true}}, NumValue: models.NullFloat64{sql.NullFloat64{Float64: 1.23, Valid: true}},
UnitTypeId: models.NullInt64{sql.NullInt64{Int64: unit_type.Id, Valid: true}}, UnitTypeId: models.NullInt64{sql.NullInt64{Int64: unit_type.Id, Valid: true}},
} }
} }

View file

@ -1,5 +1,5 @@
-- bactdb -- bactdb
-- Matthew R Dillon -- Matthew R Dillon
DROP TABLE observations; DROP TABLE characteristics;

View file

@ -1,18 +1,18 @@
-- bactdb -- bactdb
-- Matthew R Dillon -- Matthew R Dillon
CREATE TABLE observations ( CREATE TABLE characteristics (
id BIGSERIAL NOT NULL, id BIGSERIAL NOT NULL,
observation_name CHARACTER VARYING(100) NOT NULL, characteristic_name CHARACTER VARYING(100) NOT NULL,
characteristic_type_id BIGINT NOT NULL, characteristic_type_id BIGINT NOT NULL,
created_at TIMESTAMP WITH TIME ZONE NOT NULL, created_at TIMESTAMP WITH TIME ZONE NOT NULL,
updated_at TIMESTAMP WITH TIME ZONE NOT NULL, updated_at TIMESTAMP WITH TIME ZONE NOT NULL,
deleted_at TIMESTAMP WITH TIME ZONE 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) 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);

View file

@ -4,7 +4,7 @@
CREATE TABLE measurements ( CREATE TABLE measurements (
id BIGSERIAL NOT NULL, id BIGSERIAL NOT NULL,
strain_id BIGINT NOT NULL, strain_id BIGINT NOT NULL,
observation_id BIGINT NOT NULL, characteristic_id BIGINT NOT NULL,
text_measurement_type_id BIGINT NULL, text_measurement_type_id BIGINT NULL,
txt_value CHARACTER VARYING(255) NULL, txt_value CHARACTER VARYING(255) NULL,
num_value NUMERIC(8, 3) NULL, num_value NUMERIC(8, 3) NULL,
@ -16,9 +16,9 @@ CREATE TABLE measurements (
created_at TIMESTAMP WITH TIME ZONE NOT NULL, created_at TIMESTAMP WITH TIME ZONE NOT NULL,
updated_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 (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 (text_measurement_type_id) REFERENCES text_measurement_types(id),
FOREIGN KEY (unit_type_id) REFERENCES unit_types(id), FOREIGN KEY (unit_type_id) REFERENCES unit_types(id),
FOREIGN KEY (test_method_id) REFERENCES test_methods(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 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); CREATE INDEX text_measurement_type_id_idx ON measurements (text_measurement_type_id);

View file

@ -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
}

View file

@ -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")
}
}

View file

@ -20,7 +20,7 @@ type CharacteristicType struct {
func NewCharacteristicType() *CharacteristicType { func NewCharacteristicType() *CharacteristicType {
return &CharacteristicType{ return &CharacteristicType{
CharacteristicTypeName: "Test Obs Type", CharacteristicTypeName: "Test Char Type",
} }
} }

View file

@ -54,7 +54,7 @@ func TestCharacteristicTypeService_Create(t *testing.T) {
mux.HandleFunc(urlPath(t, router.CreateCharacteristicType, nil), func(w http.ResponseWriter, r *http.Request) { mux.HandleFunc(urlPath(t, router.CreateCharacteristicType, nil), func(w http.ResponseWriter, r *http.Request) {
called = true called = true
testMethod(t, r, "POST") 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) w.WriteHeader(http.StatusCreated)
writeJSON(w, want) 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) { mux.HandleFunc(urlPath(t, router.UpdateCharacteristicType, map[string]string{"Id": "1"}), func(w http.ResponseWriter, r *http.Request) {
called = true called = true
testMethod(t, r, "PUT") 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) w.WriteHeader(http.StatusOK)
writeJSON(w, want) writeJSON(w, want)
}) })
characteristic_type := newCharacteristicType() 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) updated, err := client.CharacteristicTypes.Update(characteristic_type.Id, characteristic_type)
if err != nil { if err != nil {
t.Errorf("CharacteristicTypes.Update returned error: %v", err) t.Errorf("CharacteristicTypes.Update returned error: %v", err)

204
models/characteristics.go Normal file
View file

@ -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)
}

View file

@ -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")
}
}

View file

@ -20,8 +20,8 @@ type Client struct {
Genera GeneraService Genera GeneraService
Species SpeciesService Species SpeciesService
Strains StrainsService Strains StrainsService
CharacteristicTypes CharacteristicTypesService CharacteristicTypes CharacteristicTypesService
Observations ObservationsService Characteristics CharacteristicsService
TextMeasurementTypes TextMeasurementTypesService TextMeasurementTypes TextMeasurementTypesService
UnitTypes UnitTypesService UnitTypes UnitTypesService
Measurements MeasurementsService Measurements MeasurementsService
@ -57,7 +57,7 @@ func NewClient(httpClient *http.Client) *Client {
c.Species = &speciesService{c} c.Species = &speciesService{c}
c.Strains = &strainsService{c} c.Strains = &strainsService{c}
c.CharacteristicTypes = &characteristicTypesService{c} c.CharacteristicTypes = &characteristicTypesService{c}
c.Observations = &observationsService{c} c.Characteristics = &characteristicsService{c}
c.TextMeasurementTypes = &textMeasurementTypesService{c} c.TextMeasurementTypes = &textMeasurementTypesService{c}
c.UnitTypes = &unitTypesService{c} c.UnitTypes = &unitTypesService{c}
c.Measurements = &measurementsService{c} c.Measurements = &measurementsService{c}

View file

@ -13,11 +13,11 @@ import (
// A Measurement is the main data type for this application // A Measurement is the main data type for this application
// There are two types of supported measurements: text & numerical. The table // There are two types of supported measurements: text & numerical. The table
// has a constraint that will allow one or the other for a particular // 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 { type Measurement struct {
Id int64 `json:"id,omitempty"` Id int64 `json:"id,omitempty"`
StrainId int64 `db:"strain_id" json:"strainId"` 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"` TextMeasurementTypeId NullInt64 `db:"text_measurement_type_id" json:"textMeasurementTypeId"`
TxtValue NullString `db:"txt_value" json:"txtValue"` TxtValue NullString `db:"txt_value" json:"txtValue"`
NumValue NullFloat64 `db:"num_value" json:"numValue"` NumValue NullFloat64 `db:"num_value" json:"numValue"`

View file

@ -13,7 +13,7 @@ func newMeasurement() *Measurement {
measurement := NewMeasurement() measurement := NewMeasurement()
measurement.Id = 1 measurement.Id = 1
measurement.StrainId = 1 measurement.StrainId = 1
measurement.ObservationId = 1 measurement.CharacteristicId = 1
measurement.UnitTypeId = NullInt64{sql.NullInt64{Int64: 1, Valid: true}} measurement.UnitTypeId = NullInt64{sql.NullInt64{Int64: 1, Valid: true}}
return measurement 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) { mux.HandleFunc(urlPath(t, router.CreateMeasurement, nil), func(w http.ResponseWriter, r *http.Request) {
called = true called = true
testMethod(t, r, "POST") 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) w.WriteHeader(http.StatusCreated)
writeJSON(w, want) 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) { mux.HandleFunc(urlPath(t, router.UpdateMeasurement, map[string]string{"Id": "1"}), func(w http.ResponseWriter, r *http.Request) {
called = true called = true
testMethod(t, r, "PUT") 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) w.WriteHeader(http.StatusOK)
writeJSON(w, want) writeJSON(w, want)
}) })

View file

@ -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)
}

View file

@ -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")
}
}

View file

@ -38,12 +38,12 @@ func API() *mux.Router {
m.Path("/characteristic_types/{Id:.+}").Methods("PUT").Name(UpdateCharacteristicType) m.Path("/characteristic_types/{Id:.+}").Methods("PUT").Name(UpdateCharacteristicType)
m.Path("/characteristic_types/{Id:.+}").Methods("DELETE").Name(DeleteCharacteristicType) m.Path("/characteristic_types/{Id:.+}").Methods("DELETE").Name(DeleteCharacteristicType)
// Observations // Characteristics
m.Path("/observations").Methods("GET").Name(Observations) m.Path("/characteristics").Methods("GET").Name(Characteristics)
m.Path("/observations").Methods("POST").Name(CreateObservation) m.Path("/characteristics").Methods("POST").Name(CreateCharacteristic)
m.Path("/observations/{Id:.+}").Methods("GET").Name(Observation) m.Path("/characteristics/{Id:.+}").Methods("GET").Name(Characteristic)
m.Path("/observations/{Id:.+}").Methods("PUT").Name(UpdateObservation) m.Path("/characteristics/{Id:.+}").Methods("PUT").Name(UpdateCharacteristic)
m.Path("/observations/{Id:.+}").Methods("DELETE").Name(DeleteObservation) m.Path("/characteristics/{Id:.+}").Methods("DELETE").Name(DeleteCharacteristic)
// TextMeasurementTypes // TextMeasurementTypes
m.Path("/text_measurement_types/").Methods("GET").Name(TextMeasurementTypes) m.Path("/text_measurement_types/").Methods("GET").Name(TextMeasurementTypes)

View file

@ -29,11 +29,11 @@ const (
UpdateCharacteristicType = "characteristic_type:update" UpdateCharacteristicType = "characteristic_type:update"
DeleteCharacteristicType = "characteristic_type:delete" DeleteCharacteristicType = "characteristic_type:delete"
Observation = "observation:get" Characteristic = "characteristic:get"
CreateObservation = "observation:create" CreateCharacteristic = "characteristic:create"
Observations = "observation:list" Characteristics = "characteristic:list"
UpdateObservation = "observation:update" UpdateCharacteristic = "characteristic:update"
DeleteObservation = "observation:delete" DeleteCharacteristic = "characteristic:delete"
TextMeasurementType = "text_measurement_type:get" TextMeasurementType = "text_measurement_type:get"
CreateTextMeasurementType = "text_measurement_type:create" CreateTextMeasurementType = "text_measurement_type:create"