commit 62ed9424956fda62254484373a6b8ed2321a58ac Author: Patrick McDonagh Date: Wed Sep 27 11:18:17 2017 -0500 Initial Commit diff --git a/app.go b/app.go new file mode 100644 index 0000000..9c90977 --- /dev/null +++ b/app.go @@ -0,0 +1,80 @@ +// app.go + +package main + +import ( + "database/sql" + "encoding/json" + "flag" + "fmt" + "log" + "net/http" + + _ "github.com/go-sql-driver/mysql" + "github.com/gorilla/mux" +) + +// App : holds the router and db configuration +type App struct { + Router *mux.Router + DB *sql.DB +} + +// Initialize : Initialize the app +func (a *App) Initialize(user, password, dbname string) { + connectionString := + fmt.Sprintf("%s:%s@/%s?parseTime=true", user, password, dbname) + + var err error + a.DB, err = sql.Open("mysql", connectionString) + if err != nil { + log.Fatal(err) + } + + a.Router = mux.NewRouter() + a.initializeRoutes() +} + +func respondWithError(w http.ResponseWriter, code int, message string) { + respondWithJSON(w, code, map[string]string{"error": message}) +} + +func respondWithJSON(w http.ResponseWriter, code int, payload interface{}) { + response, _ := json.Marshal(payload) + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(code) + w.Write(response) +} + +// Run : Run the app +func (a *App) Run(addr string) { + log.Fatal(http.ListenAndServe(":8000", a.Router)) +} + +// initializeRoutes : initialize all routes +func (a *App) initializeRoutes() { + + a.Router.HandleFunc("/api/v1/configs", a.getConfigs).Methods("GET") + a.Router.HandleFunc("/api/v1/config", a.createConfig).Methods("POST") + a.Router.HandleFunc("/api/v1/config/{id:[0-9]+}", a.getConfig).Methods("GET") + a.Router.HandleFunc("/api/v1/config/{id:[0-9]+}", a.updateConfig).Methods("PUT") + a.Router.HandleFunc("/api/v1/config/{id:[0-9]+}", a.deleteConfig).Methods("DELETE") + + a.Router.HandleFunc("/api/v1/dataTypes", a.getDataTypes).Methods("GET") + a.Router.HandleFunc("/api/v1/dataType", a.createDataType).Methods("POST") + a.Router.HandleFunc("/api/v1/dataType/{id:[0-9]+}", a.getDataType).Methods("GET") + a.Router.HandleFunc("/api/v1/dataType/{id:[0-9]+}", a.updateDataType).Methods("PUT") + a.Router.HandleFunc("/api/v1/dataType/{id:[0-9]+}", a.deleteDataType).Methods("DELETE") + + //Serve public files + var dir string + flag.StringVar(&dir, "dir", "./public", "the directory to serve files from. Defaults to the current dir") + flag.Parse() + a.Router.PathPrefix("/public/").Handler(http.StripPrefix("/public/", http.FileServer(http.Dir(dir)))) + + // Serve Root HTML Page + a.Router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + http.ServeFile(w, r, "./html/index.html") + }) +} diff --git a/handler_config.go b/handler_config.go new file mode 100644 index 0000000..808e030 --- /dev/null +++ b/handler_config.go @@ -0,0 +1,110 @@ +package main + +import ( + "database/sql" + "encoding/json" + "net/http" + "strconv" + + "github.com/gorilla/mux" +) + +func (a *App) getConfig(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + id, err := strconv.Atoi(vars["id"]) + if err != nil { + respondWithError(w, http.StatusBadRequest, "Invalid config ID") + return + } + + c := Config{ID: id} + if err := c.getConfig(a.DB); err != nil { + switch err { + case sql.ErrNoRows: + respondWithError(w, http.StatusNotFound, "Config not found") + default: + respondWithError(w, http.StatusInternalServerError, err.Error()) + } + return + } + + respondWithJSON(w, http.StatusOK, c) +} + +func (a *App) getConfigs(w http.ResponseWriter, r *http.Request) { + count, _ := strconv.Atoi(r.FormValue("count")) + start, _ := strconv.Atoi(r.FormValue("start")) + + if count > 10 || count < 1 { + count = 10 + } + if start < 0 { + start = 0 + } + + configs, err := getConfigs(a.DB, start, count) + if err != nil { + respondWithError(w, http.StatusInternalServerError, err.Error()) + return + } + + respondWithJSON(w, http.StatusOK, configs) +} + +func (a *App) createConfig(w http.ResponseWriter, r *http.Request) { + var c Config + decoder := json.NewDecoder(r.Body) + if err := decoder.Decode(&c); err != nil { + respondWithError(w, http.StatusBadRequest, "Invalid request payload") + return + } + defer r.Body.Close() + if err := c.createConfig(a.DB); err != nil { + respondWithError(w, http.StatusInternalServerError, err.Error()) + return + } + + respondWithJSON(w, http.StatusCreated, c) +} + +func (a *App) updateConfig(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + id, err := strconv.Atoi(vars["id"]) + if err != nil { + respondWithError(w, http.StatusBadRequest, "Invalid config ID") + return + } + + var c Config + decoder := json.NewDecoder(r.Body) + if err := decoder.Decode(&c); err != nil { + respondWithError(w, http.StatusBadRequest, "Invalid request payload") + return + } + defer r.Body.Close() + c.ID = id + + if err := c.updateConfig(a.DB); err != nil { + respondWithError(w, http.StatusInternalServerError, err.Error()) + return + } + + respondWithJSON(w, http.StatusOK, c) +} + +func (a *App) deleteConfig(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + id, err := strconv.Atoi(vars["id"]) + if err != nil { + respondWithError(w, http.StatusBadRequest, "Invalid Config ID") + return + } + + c := Config{ID: id} + if err := c.deleteConfig(a.DB); err != nil { + respondWithError(w, http.StatusInternalServerError, err.Error()) + return + } + + respondWithJSON(w, http.StatusOK, map[string]string{"result": "success"}) +} diff --git a/handler_datatype.go b/handler_datatype.go new file mode 100644 index 0000000..45824fc --- /dev/null +++ b/handler_datatype.go @@ -0,0 +1,110 @@ +package main + +import ( + "database/sql" + "encoding/json" + "net/http" + "strconv" + + "github.com/gorilla/mux" +) + +func (a *App) getDataType(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + id, err := strconv.Atoi(vars["id"]) + if err != nil { + respondWithError(w, http.StatusBadRequest, "Invalid config ID") + return + } + + c := DataType{ID: id} + if err := c.getDataType(a.DB); err != nil { + switch err { + case sql.ErrNoRows: + respondWithError(w, http.StatusNotFound, "DataType not found") + default: + respondWithError(w, http.StatusInternalServerError, err.Error()) + } + return + } + + respondWithJSON(w, http.StatusOK, c) +} + +func (a *App) getDataTypes(w http.ResponseWriter, r *http.Request) { + count, _ := strconv.Atoi(r.FormValue("count")) + start, _ := strconv.Atoi(r.FormValue("start")) + + if count > 10 || count < 1 { + count = 10 + } + if start < 0 { + start = 0 + } + + configs, err := getDataTypes(a.DB, start, count) + if err != nil { + respondWithError(w, http.StatusInternalServerError, err.Error()) + return + } + + respondWithJSON(w, http.StatusOK, configs) +} + +func (a *App) createDataType(w http.ResponseWriter, r *http.Request) { + var c DataType + decoder := json.NewDecoder(r.Body) + if err := decoder.Decode(&c); err != nil { + respondWithError(w, http.StatusBadRequest, "Invalid request payload") + return + } + defer r.Body.Close() + if err := c.createDataType(a.DB); err != nil { + respondWithError(w, http.StatusInternalServerError, err.Error()) + return + } + + respondWithJSON(w, http.StatusCreated, c) +} + +func (a *App) updateDataType(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + id, err := strconv.Atoi(vars["id"]) + if err != nil { + respondWithError(w, http.StatusBadRequest, "Invalid config ID") + return + } + + var c DataType + decoder := json.NewDecoder(r.Body) + if err := decoder.Decode(&c); err != nil { + respondWithError(w, http.StatusBadRequest, "Invalid request payload") + return + } + defer r.Body.Close() + c.ID = id + + if err := c.updateDataType(a.DB); err != nil { + respondWithError(w, http.StatusInternalServerError, err.Error()) + return + } + + respondWithJSON(w, http.StatusOK, c) +} + +func (a *App) deleteDataType(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + id, err := strconv.Atoi(vars["id"]) + if err != nil { + respondWithError(w, http.StatusBadRequest, "Invalid DataType ID") + return + } + + c := DataType{ID: id} + if err := c.deleteDataType(a.DB); err != nil { + respondWithError(w, http.StatusInternalServerError, err.Error()) + return + } + + respondWithJSON(w, http.StatusOK, map[string]string{"result": "success"}) +} diff --git a/html/index.html b/html/index.html new file mode 100644 index 0000000..def5f40 --- /dev/null +++ b/html/index.html @@ -0,0 +1,14 @@ + + + + + + + + + Lumberjack + + +
+ + \ No newline at end of file diff --git a/main.go b/main.go new file mode 100644 index 0000000..94c8453 --- /dev/null +++ b/main.go @@ -0,0 +1,14 @@ +package main + +import "os" + +func main() { + + a := App{} + a.Initialize( + os.Getenv("APP_DB_USERNAME"), + os.Getenv("APP_DB_PASSWORD"), + os.Getenv("APP_DB_NAME")) + + a.Run(":8080") +} diff --git a/model_config.go b/model_config.go new file mode 100644 index 0000000..9d6a23b --- /dev/null +++ b/model_config.go @@ -0,0 +1,78 @@ +package main + +import ( + "database/sql" + "time" +) + +// Config : holds information about a specific Config +type Config struct { + ID int `json:"id"` + Parameter string `json:"parameter"` + Val string `json:"val"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` +} + +// Configs : a list of Config items +type Configs []Config + +// getConfig : used during GET command +func (c *Config) getConfig(db *sql.DB) error { + return db.QueryRow("SELECT parameter, val, createdAt, updatedAt FROM configs WHERE id=?", c.ID).Scan(&c.Parameter, &c.Val, &c.CreatedAt, &c.UpdatedAt) +} + +// updateConfig : used during PUT command +func (c *Config) updateConfig(db *sql.DB) error { + updStmt, updErr := db.Prepare("UPDATE configs SET parameter=?, val=?, updatedAt=? WHERE id=?") + if updErr != nil { + panic(updErr.Error()) // proper error handling instead of panic in your app + } + defer updStmt.Close() // Close the statement when we leave main() / the program terminates + + _, err := updStmt.Exec(c.Parameter, c.Val, time.Now(), c.ID) + return err +} + +// deleteConfig : used during DELETE command +func (c *Config) deleteConfig(db *sql.DB) error { + _, err := db.Exec("DELETE FROM configs WHERE id=?", c.ID) + return err +} + +// createConfig : used during PUSH command +func (c *Config) createConfig(db *sql.DB) error { + stmtIns, insErr := db.Prepare("INSERT INTO configs (parameter, val, createdAt, updatedAt) VALUES (?, ?, ?, ?);") + if insErr != nil { + panic(insErr.Error()) // proper error handling instead of panic in your app + } + defer stmtIns.Close() // Close the statement when we leave main() / the program terminates + + _, err := stmtIns.Exec(c.Parameter, c.Val, time.Now(), time.Now()) + return err +} + +// getConfigs : used during GET command for all +func getConfigs(db *sql.DB, start, count int) (Configs, error) { + rows, err := db.Query( + "SELECT id, parameter, val, createdAt, updatedAt FROM configs LIMIT ? OFFSET ?", + count, start) + + if err != nil { + return nil, err + } + + defer rows.Close() + + configs := Configs{} + + for rows.Next() { + var c Config + if err := rows.Scan(&c.ID, &c.Parameter, &c.CreatedAt, &c.UpdatedAt); err != nil { + return nil, err + } + configs = append(configs, c) + } + + return configs, nil +} diff --git a/model_datatype.go b/model_datatype.go new file mode 100644 index 0000000..e8c5da5 --- /dev/null +++ b/model_datatype.go @@ -0,0 +1,78 @@ +package main + +import ( + "database/sql" + "time" +) + +// DataType : holds information about a specific DataType +type DataType struct { + ID int `json:"id"` + DataType string `json:"dataType"` + PlcType string `json:"plcType"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` +} + +// DataTypes : a list of DataType items +type DataTypes []DataType + +// getDataType : used during GET command +func (c *DataType) getDataType(db *sql.DB) error { + return db.QueryRow("SELECT dataType, plcType, createdAt, updatedAt FROM dataTypes WHERE id=?", c.ID).Scan(&c.DataType, &c.PlcType, &c.CreatedAt, &c.UpdatedAt) +} + +// updateDataType : used during PUT command +func (c *DataType) updateDataType(db *sql.DB) error { + updStmt, updErr := db.Prepare("UPDATE dataTypes SET dataType=?, plcType=?, updatedAt=? WHERE id=?") + if updErr != nil { + panic(updErr.Error()) // proper error handling instead of panic in your app + } + defer updStmt.Close() // Close the statement when we leave main() / the program terminates + + _, err := updStmt.Exec(c.DataType, c.PlcType, time.Now(), c.ID) + return err +} + +// deleteDataType : used during DELETE command +func (c *DataType) deleteDataType(db *sql.DB) error { + _, err := db.Exec("DELETE FROM dataTypes WHERE id=?", c.ID) + return err +} + +// createDataType : used during PUSH command +func (c *DataType) createDataType(db *sql.DB) error { + stmtIns, insErr := db.Prepare("INSERT INTO dataTypes (dataType, plcType, createdAt, updatedAt) VALUES (?, ?, ?, ?);") + if insErr != nil { + panic(insErr.Error()) // proper error handling instead of panic in your app + } + defer stmtIns.Close() // Close the statement when we leave main() / the program terminates + + _, err := stmtIns.Exec(c.DataType, c.PlcType, time.Now(), time.Now()) + return err +} + +// getDataTypes : used during GET command for all +func getDataTypes(db *sql.DB, start, count int) (DataTypes, error) { + rows, err := db.Query( + "SELECT id, dataType, plcType, createdAt, updatedAt FROM dataTypes LIMIT ? OFFSET ?", + count, start) + + if err != nil { + return nil, err + } + + defer rows.Close() + + dataTypes := DataTypes{} + + for rows.Next() { + var c DataType + if err := rows.Scan(&c.ID, &c.DataType, &c.CreatedAt, &c.UpdatedAt); err != nil { + return nil, err + } + dataTypes = append(dataTypes, c) + } + + return dataTypes, nil +} diff --git a/model_device.go b/model_device.go new file mode 100644 index 0000000..9d7d4e8 --- /dev/null +++ b/model_device.go @@ -0,0 +1,15 @@ +package main + +import "time" + +// Device : holds information about a specific Device +type Device struct { + ID int `json:"id"` + DeviceType DeviceType `json:"deviceType"` + Address string `json:"address"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` +} + +// Devices : a list of Device items +type Devices []Device diff --git a/model_devicetype.go b/model_devicetype.go new file mode 100644 index 0000000..d58d6cc --- /dev/null +++ b/model_devicetype.go @@ -0,0 +1,14 @@ +package main + +import "time" + +// DeviceType : holds information about a specific DeviceType +type DeviceType struct { + ID int `json:"id"` + Name string `json:"name"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` +} + +// DeviceTypes : a list of DeviceType items +type DeviceTypes []DeviceType diff --git a/model_file.go b/model_file.go new file mode 100644 index 0000000..021a9e9 --- /dev/null +++ b/model_file.go @@ -0,0 +1,16 @@ +package main + +import "time" + +// File : holds information about a specific File +type File struct { + ID int `json:"id"` + Name string `json:"name"` + Location string `json:"location"` + Description string `json:"description"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` +} + +// Files : a list of File items +type Files []File diff --git a/model_tag.go b/model_tag.go new file mode 100644 index 0000000..f4ab08b --- /dev/null +++ b/model_tag.go @@ -0,0 +1,25 @@ +package main + +import "time" + +// Tag : holds information about a specific Tag +type Tag struct { + ID int `json:"id"` + Name string `json:"name"` + TagClass TagClass `json:"tagClass"` + Tag string `json:"tag"` + Device Device `json:"device"` + Description string `json:"description"` + DataType DataType `json:"dataType"` + ChangeThreshold float32 `json:"changeThreshold"` + GuaranteeSec int `json:"guaranteeSec"` + MapFunction string `json:"mapFunction"` + Units string `json:"units"` + MinExpected float32 `json:"minExpected"` + MaxExpected float32 `json:"maxExpected"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` +} + +// Tags : a list of Tag items +type Tags []Tag diff --git a/model_tagclass.go b/model_tagclass.go new file mode 100644 index 0000000..4b1abe5 --- /dev/null +++ b/model_tagclass.go @@ -0,0 +1,15 @@ +package main + +import "time" + +// TagClass : holds information about a specific TagClass +type TagClass struct { + ID int `json:"id"` + ClassType string `json:"classType"` + Description string `json:"description"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` +} + +// TagClasses : a list of TagClass items +type TagClasses []TagClass diff --git a/public/transformed.js b/public/transformed.js new file mode 100644 index 0000000..e69de29 diff --git a/test_test.go b/test_test.go new file mode 100644 index 0000000..fb90df9 --- /dev/null +++ b/test_test.go @@ -0,0 +1,41 @@ +package main + +import ( + "net/http" + "net/http/httptest" + "os" + "testing" +) + +var a App + +func TestMain(m *testing.M) { + a = App{} + a.Initialize( + os.Getenv("TEST_DB_USERNAME"), + os.Getenv("TEST_DB_PASSWORD"), + os.Getenv("TEST_DB_NAME")) + + ensureConfigTableExists() + ensureDataTypeTableExists() + + code := m.Run() + + clearConfigTable() + clearDataTypeTable() + + os.Exit(code) +} + +func executeRequest(req *http.Request) *httptest.ResponseRecorder { + rr := httptest.NewRecorder() + a.Router.ServeHTTP(rr, req) + + return rr +} + +func checkResponseCode(t *testing.T, expected, actual int) { + if expected != actual { + t.Errorf("Expected response code %d. Got %d\n", expected, actual) + } +} diff --git a/testconfig_test.go b/testconfig_test.go new file mode 100644 index 0000000..0da5a5a --- /dev/null +++ b/testconfig_test.go @@ -0,0 +1,156 @@ +package main + +import ( + "bytes" + "encoding/json" + "log" + "net/http" + "strconv" + "testing" + "time" +) + +const configTableCreationQuery = `CREATE TABLE IF NOT EXISTS configs ( + id int(10) unsigned AUTO_INCREMENT, + parameter varchar(255), + val varchar(255), + createdAt datetime, + updatedAt datetime, + UNIQUE KEY parameter (parameter), + PRIMARY KEY (id) + )` + +func ensureConfigTableExists() { + if _, err := a.DB.Exec(configTableCreationQuery); err != nil { + log.Fatal(err) + } +} + +func clearConfigTable() { + a.DB.Exec("DELETE FROM configs") + a.DB.Exec("ALTER TABLE configs AUTO_INCREMENT = 1") +} + +func TestEmptyConfigTable(t *testing.T) { + clearConfigTable() + + req, _ := http.NewRequest("GET", "/api/v1/configs", nil) + response := executeRequest(req) + + checkResponseCode(t, http.StatusOK, response.Code) + + if body := response.Body.String(); body != "[]" { + t.Errorf("Expected an empty array. Got %s", body) + } +} + +func TestGetNonExistentConfig(t *testing.T) { + clearConfigTable() + + req, _ := http.NewRequest("GET", "/api/v1/config/11", nil) + response := executeRequest(req) + + checkResponseCode(t, http.StatusNotFound, response.Code) + + var m map[string]string + json.Unmarshal(response.Body.Bytes(), &m) + if m["error"] != "Config not found" { + t.Errorf("Expected the 'error' key of the response to be set to 'Config not found'. Got '%s'", m["error"]) + } +} + +func TestCreateConfig(t *testing.T) { + clearConfigTable() + + payload := []byte(`{"parameter":"save_all","val":"true"}`) + + req, _ := http.NewRequest("POST", "/api/v1/config", bytes.NewBuffer(payload)) + response := executeRequest(req) + + checkResponseCode(t, http.StatusCreated, response.Code) + + var m map[string]interface{} + json.Unmarshal(response.Body.Bytes(), &m) + + if m["parameter"] != "save_all" { + t.Errorf("Expected config parameter to be 'save_all'. Got '%v'", m["parameter"]) + } + + if m["val"] != "true" { + t.Errorf("Expected config val to be 'true'. Got '%v'", m["val"]) + } +} + +func TestGetConfig(t *testing.T) { + clearConfigTable() + addConfigs(1) + + req, _ := http.NewRequest("GET", "/api/v1/config/1", nil) + response := executeRequest(req) + + checkResponseCode(t, http.StatusOK, response.Code) +} + +func addConfigs(count int) { + if count < 1 { + count = 1 + } + insStmt, insErr := a.DB.Prepare("INSERT INTO configs(parameter, val, createdAt, updatedAt) VALUES(?, ?, ?, ?)") + if insErr != nil { + panic(insErr.Error()) // proper error handling instead of panic in your app + } + defer insStmt.Close() // Close the statement when we leave main() / the program terminates + + for i := 0; i < count; i++ { + insStmt.Exec("testparam"+strconv.Itoa(i), (i+1.0)*10, time.Now(), time.Now()) + } +} + +func TestUpdateConfig(t *testing.T) { + clearConfigTable() + addConfigs(1) + + req, _ := http.NewRequest("GET", "/api/v1/config/1", nil) + response := executeRequest(req) + var originalConfig map[string]interface{} + json.Unmarshal(response.Body.Bytes(), &originalConfig) + + payload := []byte(`{"parameter":"test param","val":"11.22"}`) + + req, _ = http.NewRequest("PUT", "/api/v1/config/1", bytes.NewBuffer(payload)) + response = executeRequest(req) + checkResponseCode(t, http.StatusOK, response.Code) + + var m map[string]interface{} + json.Unmarshal(response.Body.Bytes(), &m) + + if m["id"] != originalConfig["id"] { + t.Errorf("Expected the id to remain the same (%v). Got %v", originalConfig["id"], m["id"]) + } + + if m["parameter"] == originalConfig["parameter"] { + t.Errorf("Expected the parameter to change from '%v' to '%v'. Got '%v'", originalConfig["parameter"], m["parameter"], m["parameter"]) + } + + if m["val"] == originalConfig["val"] { + t.Errorf("Expected the val to change from '%v' to '%v'. Got '%v'", originalConfig["val"], m["val"], m["val"]) + } +} + +func TestDeleteConfig(t *testing.T) { + clearConfigTable() + addConfigs(1) + + req, _ := http.NewRequest("GET", "/api/v1/config/1", nil) + response := executeRequest(req) + checkResponseCode(t, http.StatusOK, response.Code) + + req, _ = http.NewRequest("DELETE", "/api/v1/config/1", nil) + response = executeRequest(req) + + checkResponseCode(t, http.StatusOK, response.Code) + + req, _ = http.NewRequest("GET", "/api/v1/config/1", nil) + response = executeRequest(req) + checkResponseCode(t, http.StatusNotFound, response.Code) +} diff --git a/testdatatype_test.go b/testdatatype_test.go new file mode 100644 index 0000000..64503b3 --- /dev/null +++ b/testdatatype_test.go @@ -0,0 +1,156 @@ +package main + +import ( + "bytes" + "encoding/json" + "log" + "net/http" + "strconv" + "testing" + "time" +) + +const dataTypeTableCreationQuery = `CREATE TABLE IF NOT EXISTS dataTypes ( + id int(10) unsigned AUTO_INCREMENT, + dataType varchar(255), + plcType varchar(255), + createdAt datetime, + updatedAt datetime, + UNIQUE KEY dataType (dataType), + PRIMARY KEY (id) + )` + +func ensureDataTypeTableExists() { + if _, err := a.DB.Exec(dataTypeTableCreationQuery); err != nil { + log.Fatal(err) + } +} + +func clearDataTypeTable() { + a.DB.Exec("DELETE FROM dataTypes") + a.DB.Exec("ALTER TABLE dataTypes AUTO_INCREMENT = 1") +} + +func TestEmptyDataTypeTable(t *testing.T) { + clearDataTypeTable() + + req, _ := http.NewRequest("GET", "/api/v1/dataTypes", nil) + response := executeRequest(req) + + checkResponseCode(t, http.StatusOK, response.Code) + + if body := response.Body.String(); body != "[]" { + t.Errorf("Expected an empty array. Got %s", body) + } +} + +func TestGetNonExistentDataType(t *testing.T) { + clearDataTypeTable() + + req, _ := http.NewRequest("GET", "/api/v1/dataType/11", nil) + response := executeRequest(req) + + checkResponseCode(t, http.StatusNotFound, response.Code) + + var m map[string]string + json.Unmarshal(response.Body.Bytes(), &m) + if m["error"] != "DataType not found" { + t.Errorf("Expected the 'error' key of the response to be set to 'DataType not found'. Got '%s'", m["error"]) + } +} + +func TestCreateDataType(t *testing.T) { + clearDataTypeTable() + + payload := []byte(`{"dataType":"Floating Point","plcType":"REAL"}`) + + req, _ := http.NewRequest("POST", "/api/v1/dataType", bytes.NewBuffer(payload)) + response := executeRequest(req) + + checkResponseCode(t, http.StatusCreated, response.Code) + + var m map[string]interface{} + json.Unmarshal(response.Body.Bytes(), &m) + + if m["dataType"] != "Floating Point" { + t.Errorf("Expected dataType dataType to be 'Floating Point'. Got '%v'", m["dataType"]) + } + + if m["plcType"] != "REAL" { + t.Errorf("Expected dataType plcType to be 'REAL'. Got '%v'", m["plcType"]) + } +} + +func TestGetDataType(t *testing.T) { + clearDataTypeTable() + addDataTypes(1) + + req, _ := http.NewRequest("GET", "/api/v1/dataType/1", nil) + response := executeRequest(req) + + checkResponseCode(t, http.StatusOK, response.Code) +} + +func addDataTypes(count int) { + if count < 1 { + count = 1 + } + insStmt, insErr := a.DB.Prepare("INSERT INTO dataTypes(dataType, plcType, createdAt, updatedAt) VALUES(?, ?, ?, ?)") + if insErr != nil { + panic(insErr.Error()) // proper error handling instead of panic in your app + } + defer insStmt.Close() // Close the statement when we leave main() / the program terminates + + for i := 0; i < count; i++ { + insStmt.Exec("TESTTYPE"+strconv.Itoa(i), "TESTPLCTYPE"+strconv.Itoa(i), time.Now(), time.Now()) + } +} + +func TestUpdateDataType(t *testing.T) { + clearDataTypeTable() + addDataTypes(1) + + req, _ := http.NewRequest("GET", "/api/v1/dataType/1", nil) + response := executeRequest(req) + var originalDataType map[string]interface{} + json.Unmarshal(response.Body.Bytes(), &originalDataType) + + payload := []byte(`{"dataType":"Floating Point","plcType":"REAL"}`) + + req, _ = http.NewRequest("PUT", "/api/v1/dataType/1", bytes.NewBuffer(payload)) + response = executeRequest(req) + checkResponseCode(t, http.StatusOK, response.Code) + + var m map[string]interface{} + json.Unmarshal(response.Body.Bytes(), &m) + + if m["id"] != originalDataType["id"] { + t.Errorf("Expected the id to remain the same (%v). Got %v", originalDataType["id"], m["id"]) + } + + if m["dataType"] == originalDataType["dataType"] { + t.Errorf("Expected the dataType to change from '%v' to '%v'. Got '%v'", originalDataType["dataType"], m["dataType"], m["dataType"]) + } + + if m["plcType"] == originalDataType["plcType"] { + t.Errorf("Expected the plcType to change from '%v' to '%v'. Got '%v'", originalDataType["plcType"], m["plcType"], m["plcType"]) + } +} + +func TestDeleteDataType(t *testing.T) { + clearDataTypeTable() + addDataTypes(1) + + req, _ := http.NewRequest("GET", "/api/v1/dataType/1", nil) + response := executeRequest(req) + checkResponseCode(t, http.StatusOK, response.Code) + + req, _ = http.NewRequest("DELETE", "/api/v1/dataType/1", nil) + response = executeRequest(req) + + checkResponseCode(t, http.StatusOK, response.Code) + + req, _ = http.NewRequest("GET", "/api/v1/dataType/1", nil) + response = executeRequest(req) + checkResponseCode(t, http.StatusNotFound, response.Code) +}