feat: add CRUD endpoints for sensors, actors, and sensor readings; update README and integration tests
This commit is contained in:
@@ -0,0 +1,202 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/DATA-DOG/go-sqlmock"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// helper to build gin engine with given handler without auth for focused handler tests
|
||||
func newTestRouter(h *Handler, register func(*gin.Engine)) *gin.Engine {
|
||||
gin.SetMode(gin.TestMode)
|
||||
r := gin.New()
|
||||
register(r)
|
||||
return r
|
||||
}
|
||||
|
||||
// helper to set up sqlmock-backed handler
|
||||
func newMockHandler(t *testing.T) (*Handler, sqlmock.Sqlmock, func()) {
|
||||
t.Helper()
|
||||
db, mock, err := sqlmock.New()
|
||||
if err != nil {
|
||||
t.Fatalf("sqlmock: %v", err)
|
||||
}
|
||||
h := &Handler{DB: db, JWTSecret: "secret"}
|
||||
cleanup := func() { db.Close() }
|
||||
return h, mock, cleanup
|
||||
}
|
||||
|
||||
func TestCreateSensor(t *testing.T) {
|
||||
h, mock, cleanup := newMockHandler(t)
|
||||
defer cleanup()
|
||||
|
||||
mock.ExpectExec("INSERT INTO sensors").
|
||||
WithArgs(sqlmock.AnyArg(), sqlmock.AnyArg(), "temp", "bool", 1).
|
||||
WillReturnResult(sqlmock.NewResult(0, 1))
|
||||
|
||||
r := newTestRouter(h, func(r *gin.Engine) {
|
||||
r.POST("/sensors", h.CreateSensor)
|
||||
})
|
||||
|
||||
body := map[string]interface{}{
|
||||
"device_id": "11111111-1111-1111-1111-111111111111",
|
||||
"name": "temp",
|
||||
"type": "bool",
|
||||
"data_type_id": 1,
|
||||
}
|
||||
buf, _ := json.Marshal(body)
|
||||
req := httptest.NewRequest(http.MethodPost, "/sensors", bytes.NewReader(buf))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusCreated {
|
||||
t.Fatalf("expected 201 got %d, body=%s", w.Code, w.Body.String())
|
||||
}
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Fatalf("expectations: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetSensors(t *testing.T) {
|
||||
h, mock, cleanup := newMockHandler(t)
|
||||
defer cleanup()
|
||||
|
||||
rows := sqlmock.NewRows([]string{"id", "device_id", "name", "type", "data_type_id", "created_at", "updated_at"}).
|
||||
AddRow("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb", "temp", "bool", 1, time.Now(), time.Now())
|
||||
|
||||
mock.ExpectQuery(`SELECT BIN_TO_UUID\(id\)`).WillReturnRows(rows)
|
||||
|
||||
r := newTestRouter(h, func(r *gin.Engine) {
|
||||
r.GET("/sensors", h.GetSensors)
|
||||
})
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/sensors", nil)
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("expected 200 got %d, body=%s", w.Code, w.Body.String())
|
||||
}
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Fatalf("expectations: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateActor(t *testing.T) {
|
||||
h, mock, cleanup := newMockHandler(t)
|
||||
defer cleanup()
|
||||
|
||||
mock.ExpectExec("INSERT INTO actors").
|
||||
WithArgs(sqlmock.AnyArg(), sqlmock.AnyArg(), "led", "switch", 1).
|
||||
WillReturnResult(sqlmock.NewResult(0, 1))
|
||||
|
||||
r := newTestRouter(h, func(r *gin.Engine) {
|
||||
r.POST("/actors", h.CreateActor)
|
||||
})
|
||||
|
||||
body := map[string]interface{}{
|
||||
"device_id": "11111111-1111-1111-1111-111111111111",
|
||||
"name": "led",
|
||||
"type": "switch",
|
||||
"data_type_id": 1,
|
||||
}
|
||||
buf, _ := json.Marshal(body)
|
||||
req := httptest.NewRequest(http.MethodPost, "/actors", bytes.NewReader(buf))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusCreated {
|
||||
t.Fatalf("expected 201 got %d, body=%s", w.Code, w.Body.String())
|
||||
}
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Fatalf("expectations: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetActors(t *testing.T) {
|
||||
h, mock, cleanup := newMockHandler(t)
|
||||
defer cleanup()
|
||||
|
||||
rows := sqlmock.NewRows([]string{"id", "device_id", "name", "type", "data_type_id", "created_at", "updated_at"}).
|
||||
AddRow("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb", "led", "switch", 1, time.Now(), time.Now())
|
||||
|
||||
mock.ExpectQuery(`SELECT BIN_TO_UUID\(id\).*FROM actors`).WillReturnRows(rows)
|
||||
|
||||
r := newTestRouter(h, func(r *gin.Engine) {
|
||||
r.GET("/actors", h.GetActors)
|
||||
})
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/actors", nil)
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("expected 200 got %d, body=%s", w.Code, w.Body.String())
|
||||
}
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Fatalf("expectations: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateSensorReading(t *testing.T) {
|
||||
h, mock, cleanup := newMockHandler(t)
|
||||
defer cleanup()
|
||||
|
||||
mock.ExpectExec("INSERT INTO sensor_readings").
|
||||
WithArgs(sqlmock.AnyArg(), 42.0, sqlmock.AnyArg()).
|
||||
WillReturnResult(sqlmock.NewResult(10, 1))
|
||||
|
||||
r := newTestRouter(h, func(r *gin.Engine) {
|
||||
r.POST("/sensor-readings", h.CreateSensorReading)
|
||||
})
|
||||
|
||||
body := map[string]interface{}{
|
||||
"sensor_id": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
|
||||
"value": 42.0,
|
||||
}
|
||||
buf, _ := json.Marshal(body)
|
||||
req := httptest.NewRequest(http.MethodPost, "/sensor-readings", bytes.NewReader(buf))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusCreated {
|
||||
t.Fatalf("expected 201 got %d, body=%s", w.Code, w.Body.String())
|
||||
}
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Fatalf("expectations: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetSensorReadings(t *testing.T) {
|
||||
h, mock, cleanup := newMockHandler(t)
|
||||
defer cleanup()
|
||||
|
||||
rows := sqlmock.NewRows([]string{"id", "sensor_id", "value", "value_at"}).
|
||||
AddRow(int64(1), "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", 1.23, time.Now())
|
||||
|
||||
mock.ExpectQuery(`SELECT id, BIN_TO_UUID\(sensor_id\)`).WillReturnRows(rows)
|
||||
|
||||
r := newTestRouter(h, func(r *gin.Engine) {
|
||||
r.GET("/sensor-readings", h.GetSensorReadings)
|
||||
})
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/sensor-readings", nil)
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("expected 200 got %d, body=%s", w.Code, w.Body.String())
|
||||
}
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Fatalf("expectations: %v", err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user