171 lines
5.8 KiB
Go
171 lines
5.8 KiB
Go
package handler
|
|
|
|
import (
|
|
"net/http/httptest"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/DATA-DOG/go-sqlmock"
|
|
"github.com/gin-gonic/gin"
|
|
"gorm.io/driver/postgres"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
func TestCanAccessRoomAdmin(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
rec := httptest.NewRecorder()
|
|
c, _ := gin.CreateTestContext(rec)
|
|
c.Set("user", map[string]interface{}{"sub": "admin-1", "role": "admin"})
|
|
roomID := "room-1"
|
|
if !canAccessRoom(nil, c, &roomID) {
|
|
t.Fatal("admin should have global object access")
|
|
}
|
|
}
|
|
|
|
func TestCanAccessRoomMissingUser(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
rec := httptest.NewRecorder()
|
|
c, _ := gin.CreateTestContext(rec)
|
|
roomID := "room-1"
|
|
if canAccessRoom(nil, c, &roomID) {
|
|
t.Fatal("missing user should not pass room access")
|
|
}
|
|
}
|
|
|
|
func TestApplyRoomScopeUsesOrganizationMembership(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
sqlDB, mock, err := sqlmock.New()
|
|
if err != nil {
|
|
t.Fatalf("create sqlmock: %v", err)
|
|
}
|
|
defer sqlDB.Close()
|
|
db, err := gorm.Open(postgres.New(postgres.Config{Conn: sqlDB}), &gorm.Config{})
|
|
if err != nil {
|
|
t.Fatalf("open gorm: %v", err)
|
|
}
|
|
rec := httptest.NewRecorder()
|
|
c, _ := gin.CreateTestContext(rec)
|
|
c.Set("user", map[string]interface{}{"sub": "user-1", "role": "viewer"})
|
|
|
|
mock.ExpectQuery(".*CAST\\(room_id AS text\\).*organization_members.*").
|
|
WithArgs("user-1").
|
|
WillReturnRows(sqlmock.NewRows([]string{"id", "name", "org_id"}))
|
|
|
|
var rooms []map[string]interface{}
|
|
q := applyRoomScope(db.Model(&struct{}{}).Table("rooms"), c, "room_id")
|
|
if err := q.Find(&rooms).Error; err != nil {
|
|
t.Fatalf("scoped query failed: %v", err)
|
|
}
|
|
if err := mock.ExpectationsWereMet(); err != nil {
|
|
t.Fatalf("unmet sql expectations: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestCanAccessDeviceKeyAdmin(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
rec := httptest.NewRecorder()
|
|
c, _ := gin.CreateTestContext(rec)
|
|
c.Set("user", map[string]interface{}{"sub": "admin-1", "role": "admin"})
|
|
if !canAccessDeviceKey(nil, c, "device-a") {
|
|
t.Fatal("admin should have global device access")
|
|
}
|
|
}
|
|
|
|
func TestCanAccessDeviceKeyMissingUser(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
rec := httptest.NewRecorder()
|
|
c, _ := gin.CreateTestContext(rec)
|
|
if canAccessDeviceKey(nil, c, "device-a") {
|
|
t.Fatal("missing user should not pass device access")
|
|
}
|
|
}
|
|
|
|
func TestObjectRoomIDSensorResolvesThroughDevice(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
sqlDB, mock, err := sqlmock.New()
|
|
if err != nil {
|
|
t.Fatalf("create sqlmock: %v", err)
|
|
}
|
|
defer sqlDB.Close()
|
|
db, err := gorm.Open(postgres.New(postgres.Config{Conn: sqlDB}), &gorm.Config{})
|
|
if err != nil {
|
|
t.Fatalf("open gorm: %v", err)
|
|
}
|
|
|
|
mock.ExpectQuery(`.*FROM "sensors".*WHERE id = \$1.*`).
|
|
WillReturnRows(sqlmock.NewRows([]string{"id", "name", "metric", "unit", "data_type", "device_id", "created_at"}).
|
|
AddRow("sensor-1", "温度", "temperature", nil, nil, "device-1", time.Now()))
|
|
mock.ExpectQuery(`.*FROM "devices".*WHERE id = \$1.*`).
|
|
WillReturnRows(sqlmock.NewRows([]string{"id", "device_key", "name", "kind", "model", "firmware", "online_status", "last_seen", "room_id", "topic", "created_at", "updated_at"}).
|
|
AddRow("device-1", "device-a", "温度设备", "sensor", nil, nil, "online", nil, "room-1", nil, time.Now(), time.Now()))
|
|
|
|
roomID, ok := objectRoomID(db, "sensor", "sensor-1")
|
|
if !ok || roomID == nil || *roomID != "room-1" {
|
|
t.Fatalf("sensor room = %v, ok = %v, want room-1", roomID, ok)
|
|
}
|
|
if err := mock.ExpectationsWereMet(); err != nil {
|
|
t.Fatalf("unmet sql expectations: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestRequireObjectAccessForbiddenForForeignDevice(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
sqlDB, mock, err := sqlmock.New()
|
|
if err != nil {
|
|
t.Fatalf("create sqlmock: %v", err)
|
|
}
|
|
defer sqlDB.Close()
|
|
db, err := gorm.Open(postgres.New(postgres.Config{Conn: sqlDB}), &gorm.Config{})
|
|
if err != nil {
|
|
t.Fatalf("open gorm: %v", err)
|
|
}
|
|
rec := httptest.NewRecorder()
|
|
c, _ := gin.CreateTestContext(rec)
|
|
c.Set("user", map[string]interface{}{"sub": "user-1", "role": "viewer"})
|
|
|
|
mock.ExpectQuery(`.*FROM "devices".*WHERE id = \$1.*`).
|
|
WillReturnRows(sqlmock.NewRows([]string{"id", "device_key", "name", "kind", "model", "firmware", "online_status", "last_seen", "room_id", "topic", "created_at", "updated_at"}).
|
|
AddRow("device-1", "device-a", "温度设备", "sensor", nil, nil, "online", nil, "room-1", nil, time.Now(), time.Now()))
|
|
mock.ExpectQuery(`.*FROM "rooms".*organization_members.*`).
|
|
WithArgs("room-1", "user-1").
|
|
WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(0))
|
|
|
|
if requireObjectAccess(c, db, "device", "device-1") {
|
|
t.Fatal("foreign device should not pass object access")
|
|
}
|
|
if rec.Code != 403 {
|
|
t.Fatalf("status = %d, want 403", rec.Code)
|
|
}
|
|
if err := mock.ExpectationsWereMet(); err != nil {
|
|
t.Fatalf("unmet sql expectations: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestFirstAccessibleRoomIDUsesOrgScope(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
sqlDB, mock, err := sqlmock.New()
|
|
if err != nil {
|
|
t.Fatalf("create sqlmock: %v", err)
|
|
}
|
|
defer sqlDB.Close()
|
|
db, err := gorm.Open(postgres.New(postgres.Config{Conn: sqlDB}), &gorm.Config{})
|
|
if err != nil {
|
|
t.Fatalf("open gorm: %v", err)
|
|
}
|
|
rec := httptest.NewRecorder()
|
|
c, _ := gin.CreateTestContext(rec)
|
|
c.Set("user", map[string]interface{}{"sub": "user-1", "role": "viewer"})
|
|
|
|
mock.ExpectQuery(`.*FROM "rooms".*CAST\(org_id AS text\).*organization_members.*`).
|
|
WillReturnRows(sqlmock.NewRows([]string{"id", "name", "code", "location", "description", "capacity", "stage", "region", "org_id", "status", "created_at", "updated_at"}).
|
|
AddRow("room-1", "一号蚕房", nil, nil, nil, nil, nil, nil, "org-1", "active", time.Now(), time.Now()))
|
|
|
|
roomID := firstAccessibleRoomID(db, c)
|
|
if roomID == nil || *roomID != "room-1" {
|
|
t.Fatalf("first accessible room = %v, want room-1", roomID)
|
|
}
|
|
if err := mock.ExpectationsWereMet(); err != nil {
|
|
t.Fatalf("unmet sql expectations: %v", err)
|
|
}
|
|
}
|