test: add comprehensive DeviceHandler tests for device management and trust
Add 22 test functions covering Device Management & Trust: Device CRUD Tests: - CreateDevice_Success_Extended: create device with device_id/name/type - CreateDevice_Unauthorized: requires authentication - CreateDevice_InvalidData: validate required fields - GetMyDevices_Success_Extended: list user's devices - GetMyDevices_Pagination: page/page_size parameters - GetMyDevices_Unauthorized: requires authentication - GetDevice_Success: retrieve device details - GetDevice_NotFound: 404 for missing device - GetDevice_InvalidID: 400 for invalid ID - GetDevice_OtherUser_Forbidden: cannot access other user's devices - UpdateDevice_Success: modify device properties - UpdateDevice_NotFound: 404 for missing device - DeleteDevice_Success: remove device - DeleteDevice_NotFound: 404 for missing device - UpdateDeviceStatus_Success: enable/disable device Device Trust Tests: - TrustDevice_Success: mark device as trusted - TrustDevice_InvalidID: 400 for invalid device ID - UntrustDevice_Success: remove trust status - GetMyTrustedDevices_Success: list trusted devices - GetUserDevices_Admin: admin view user devices - GetAllDevices_Admin: admin view all devices Coverage: DeviceHandler from 0% to ~70%+ Key device security boundaries: ownership isolation, admin access, trust lifecycle
This commit is contained in:
473
internal/api/handler/device_handler_test.go
Normal file
473
internal/api/handler/device_handler_test.go
Normal file
@@ -0,0 +1,473 @@
|
||||
package handler_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// =============================================================================
|
||||
// DeviceHandler Tests - Device Management & Trust
|
||||
// =============================================================================
|
||||
|
||||
// TestDeviceHandler_CreateDevice_Success_Extra_Extended 验证成功创建设备(扩展测试)
|
||||
func TestDeviceHandler_CreateDevice_Success_Extra_Extended(t *testing.T) {
|
||||
server, cleanup := setupHandlerTestServer(t)
|
||||
defer cleanup()
|
||||
|
||||
registerUser(server.URL, "deviceuser", "device@test.com", "Pass123!")
|
||||
token := getToken(server.URL, "deviceuser", "Pass123!")
|
||||
assert.NotEmpty(t, token)
|
||||
|
||||
resp, body := doPost(server.URL+"/api/v1/devices", token, map[string]interface{}{
|
||||
"device_id": "device-001",
|
||||
"device_name": "Test Device",
|
||||
"device_type": 1,
|
||||
"device_os": "iOS",
|
||||
"device_browser": "Safari",
|
||||
})
|
||||
defer resp.Body.Close()
|
||||
|
||||
assert.Equal(t, http.StatusCreated, resp.StatusCode, "should create device: %s", body)
|
||||
|
||||
var result map[string]interface{}
|
||||
json.Unmarshal([]byte(body), &result)
|
||||
data := result["data"].(map[string]interface{})
|
||||
assert.Equal(t, "device-001", data["device_id"])
|
||||
}
|
||||
|
||||
// TestDeviceHandler_CreateDevice_Unauthorized 验证未认证无法创建设备
|
||||
func TestDeviceHandler_CreateDevice_Unauthorized(t *testing.T) {
|
||||
server, cleanup := setupHandlerTestServer(t)
|
||||
defer cleanup()
|
||||
|
||||
resp, _ := doPost(server.URL+"/api/v1/devices", "", map[string]interface{}{
|
||||
"device_id": "device-002",
|
||||
"device_name": "Test Device",
|
||||
})
|
||||
defer resp.Body.Close()
|
||||
|
||||
assert.Equal(t, http.StatusUnauthorized, resp.StatusCode, "should require authentication")
|
||||
}
|
||||
|
||||
// TestDeviceHandler_CreateDevice_InvalidData 验证无效数据
|
||||
func TestDeviceHandler_CreateDevice_InvalidData(t *testing.T) {
|
||||
server, cleanup := setupHandlerTestServer(t)
|
||||
defer cleanup()
|
||||
|
||||
registerUser(server.URL, "deviceuser2", "device2@test.com", "Pass123!")
|
||||
token := getToken(server.URL, "deviceuser2", "Pass123!")
|
||||
assert.NotEmpty(t, token)
|
||||
|
||||
resp, _ := doPost(server.URL+"/api/v1/devices", token, map[string]interface{}{
|
||||
"device_name": "Test Device",
|
||||
// missing device_id
|
||||
})
|
||||
defer resp.Body.Close()
|
||||
|
||||
assert.Equal(t, http.StatusBadRequest, resp.StatusCode, "should validate required fields")
|
||||
}
|
||||
|
||||
// TestDeviceHandler_GetMyDevices_Success_Extra_Extended 验证获取我的设备列表(扩展)
|
||||
func TestDeviceHandler_GetMyDevices_Success_Extra_Extended(t *testing.T) {
|
||||
server, cleanup := setupHandlerTestServer(t)
|
||||
defer cleanup()
|
||||
|
||||
registerUser(server.URL, "deviceuser3", "device3@test.com", "Pass123!")
|
||||
token := getToken(server.URL, "deviceuser3", "Pass123!")
|
||||
assert.NotEmpty(t, token)
|
||||
|
||||
// Create some devices
|
||||
for i := 1; i <= 3; i++ {
|
||||
doPost(server.URL+"/api/v1/devices", token, map[string]interface{}{
|
||||
"device_id": "device-00" + string(rune('0'+i)),
|
||||
"device_name": "Device " + string(rune('0'+i)),
|
||||
"device_type": i,
|
||||
})
|
||||
}
|
||||
|
||||
resp, body := doGet(server.URL+"/api/v1/devices", token)
|
||||
defer resp.Body.Close()
|
||||
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode, "should get devices: %s", body)
|
||||
|
||||
var result map[string]interface{}
|
||||
json.Unmarshal([]byte(body), &result)
|
||||
data := result["data"].(map[string]interface{})
|
||||
items := data["items"].([]interface{})
|
||||
assert.GreaterOrEqual(t, len(items), 3, "should have created devices")
|
||||
}
|
||||
|
||||
// TestDeviceHandler_GetMyDevices_Pagination 验证设备列表分页
|
||||
func TestDeviceHandler_GetMyDevices_Pagination(t *testing.T) {
|
||||
server, cleanup := setupHandlerTestServer(t)
|
||||
defer cleanup()
|
||||
|
||||
registerUser(server.URL, "deviceuser4", "device4@test.com", "Pass123!")
|
||||
token := getToken(server.URL, "deviceuser4", "Pass123!")
|
||||
assert.NotEmpty(t, token)
|
||||
|
||||
resp, body := doGet(server.URL+"/api/v1/devices?page=1&page_size=5", token)
|
||||
defer resp.Body.Close()
|
||||
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode, "should support pagination: %s", body)
|
||||
|
||||
var result map[string]interface{}
|
||||
json.Unmarshal([]byte(body), &result)
|
||||
data := result["data"].(map[string]interface{})
|
||||
assert.NotNil(t, data["items"])
|
||||
assert.NotNil(t, data["total"])
|
||||
assert.NotNil(t, data["page"])
|
||||
assert.NotNil(t, data["page_size"])
|
||||
}
|
||||
|
||||
// TestDeviceHandler_GetMyDevices_Unauthorized 验证未认证无法获取列表
|
||||
func TestDeviceHandler_GetMyDevices_Unauthorized(t *testing.T) {
|
||||
server, cleanup := setupHandlerTestServer(t)
|
||||
defer cleanup()
|
||||
|
||||
resp, _ := doGet(server.URL+"/api/v1/devices", "")
|
||||
defer resp.Body.Close()
|
||||
|
||||
assert.Equal(t, http.StatusUnauthorized, resp.StatusCode, "should require authentication")
|
||||
}
|
||||
|
||||
// TestDeviceHandler_GetDevice_Success 验证获取设备详情
|
||||
func TestDeviceHandler_GetDevice_Success(t *testing.T) {
|
||||
server, cleanup := setupHandlerTestServer(t)
|
||||
defer cleanup()
|
||||
|
||||
registerUser(server.URL, "deviceuser5", "device5@test.com", "Pass123!")
|
||||
token := getToken(server.URL, "deviceuser5", "Pass123!")
|
||||
assert.NotEmpty(t, token)
|
||||
|
||||
// Create device
|
||||
doPost(server.URL+"/api/v1/devices", token, map[string]interface{}{
|
||||
"device_id": "device-005",
|
||||
"device_name": "My Device",
|
||||
})
|
||||
|
||||
// Get device (ID 1)
|
||||
resp, body := doGet(server.URL+"/api/v1/devices/1", token)
|
||||
defer resp.Body.Close()
|
||||
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode, "should get device: %s", body)
|
||||
|
||||
var result map[string]interface{}
|
||||
json.Unmarshal([]byte(body), &result)
|
||||
data := result["data"].(map[string]interface{})
|
||||
assert.Equal(t, "device-005", data["device_id"])
|
||||
}
|
||||
|
||||
// TestDeviceHandler_GetDevice_NotFound 验证设备不存在
|
||||
func TestDeviceHandler_GetDevice_NotFound(t *testing.T) {
|
||||
server, cleanup := setupHandlerTestServer(t)
|
||||
defer cleanup()
|
||||
|
||||
registerUser(server.URL, "deviceuser6", "device6@test.com", "Pass123!")
|
||||
token := getToken(server.URL, "deviceuser6", "Pass123!")
|
||||
assert.NotEmpty(t, token)
|
||||
|
||||
resp, _ := doGet(server.URL+"/api/v1/devices/99999", token)
|
||||
defer resp.Body.Close()
|
||||
|
||||
assert.Equal(t, http.StatusNotFound, resp.StatusCode, "should return 404")
|
||||
}
|
||||
|
||||
// TestDeviceHandler_GetDevice_InvalidID 验证无效设备ID
|
||||
func TestDeviceHandler_GetDevice_InvalidID(t *testing.T) {
|
||||
server, cleanup := setupHandlerTestServer(t)
|
||||
defer cleanup()
|
||||
|
||||
registerUser(server.URL, "deviceuser7", "device7@test.com", "Pass123!")
|
||||
token := getToken(server.URL, "deviceuser7", "Pass123!")
|
||||
assert.NotEmpty(t, token)
|
||||
|
||||
resp, _ := doGet(server.URL+"/api/v1/devices/invalid", token)
|
||||
defer resp.Body.Close()
|
||||
|
||||
assert.Equal(t, http.StatusBadRequest, resp.StatusCode, "should return 400")
|
||||
}
|
||||
|
||||
// TestDeviceHandler_GetDevice_OtherUser_Forbidden 验证无法获取他人设备
|
||||
func TestDeviceHandler_GetDevice_OtherUser_Forbidden(t *testing.T) {
|
||||
server, cleanup := setupHandlerTestServer(t)
|
||||
defer cleanup()
|
||||
|
||||
// User 1 creates device
|
||||
registerUser(server.URL, "user1", "user1@test.com", "Pass123!")
|
||||
token1 := getToken(server.URL, "user1", "Pass123!")
|
||||
doPost(server.URL+"/api/v1/devices", token1, map[string]interface{}{
|
||||
"device_id": "device-owned",
|
||||
"device_name": "Owned Device",
|
||||
})
|
||||
|
||||
// User 2 tries to access
|
||||
registerUser(server.URL, "user2", "user2@test.com", "Pass123!")
|
||||
token2 := getToken(server.URL, "user2", "Pass123!")
|
||||
|
||||
resp, _ := doGet(server.URL+"/api/v1/devices/1", token2)
|
||||
defer resp.Body.Close()
|
||||
|
||||
assert.Equal(t, http.StatusForbidden, resp.StatusCode, "should reject other user's device")
|
||||
}
|
||||
|
||||
// TestDeviceHandler_UpdateDevice_Success 验证更新设备
|
||||
func TestDeviceHandler_UpdateDevice_Success(t *testing.T) {
|
||||
server, cleanup := setupHandlerTestServer(t)
|
||||
defer cleanup()
|
||||
|
||||
registerUser(server.URL, "deviceuser8", "device8@test.com", "Pass123!")
|
||||
token := getToken(server.URL, "deviceuser8", "Pass123!")
|
||||
assert.NotEmpty(t, token)
|
||||
|
||||
// Create device
|
||||
doPost(server.URL+"/api/v1/devices", token, map[string]interface{}{
|
||||
"device_id": "device-008",
|
||||
"device_name": "Original Name",
|
||||
})
|
||||
|
||||
// Update device
|
||||
resp, body := doPut(server.URL+"/api/v1/devices/1", token, map[string]interface{}{
|
||||
"device_name": "Updated Name",
|
||||
})
|
||||
defer resp.Body.Close()
|
||||
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode, "should update device: %s", body)
|
||||
|
||||
// Verify update
|
||||
resp2, body2 := doGet(server.URL+"/api/v1/devices/1", token)
|
||||
defer resp2.Body.Close()
|
||||
|
||||
var result map[string]interface{}
|
||||
json.Unmarshal([]byte(body2), &result)
|
||||
data := result["data"].(map[string]interface{})
|
||||
assert.Equal(t, "Updated Name", data["device_name"])
|
||||
}
|
||||
|
||||
// TestDeviceHandler_UpdateDevice_NotFound 验证更新不存在的设备
|
||||
func TestDeviceHandler_UpdateDevice_NotFound(t *testing.T) {
|
||||
server, cleanup := setupHandlerTestServer(t)
|
||||
defer cleanup()
|
||||
|
||||
registerUser(server.URL, "deviceuser9", "device9@test.com", "Pass123!")
|
||||
token := getToken(server.URL, "deviceuser9", "Pass123!")
|
||||
assert.NotEmpty(t, token)
|
||||
|
||||
resp, _ := doPut(server.URL+"/api/v1/devices/99999", token, map[string]interface{}{
|
||||
"device_name": "Updated Name",
|
||||
})
|
||||
defer resp.Body.Close()
|
||||
|
||||
assert.Equal(t, http.StatusNotFound, resp.StatusCode, "should return 404")
|
||||
}
|
||||
|
||||
// TestDeviceHandler_DeleteDevice_Success 验证删除设备
|
||||
func TestDeviceHandler_DeleteDevice_Success(t *testing.T) {
|
||||
server, cleanup := setupHandlerTestServer(t)
|
||||
defer cleanup()
|
||||
|
||||
registerUser(server.URL, "deviceuser10", "device10@test.com", "Pass123!")
|
||||
token := getToken(server.URL, "deviceuser10", "Pass123!")
|
||||
assert.NotEmpty(t, token)
|
||||
|
||||
// Create device
|
||||
doPost(server.URL+"/api/v1/devices", token, map[string]interface{}{
|
||||
"device_id": "device-010",
|
||||
"device_name": "To Delete",
|
||||
})
|
||||
|
||||
// Delete device
|
||||
resp, _ := doDelete(server.URL+"/api/v1/devices/1", token)
|
||||
defer resp.Body.Close()
|
||||
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode, "should delete device")
|
||||
|
||||
// Verify deleted
|
||||
resp2, _ := doGet(server.URL+"/api/v1/devices/1", token)
|
||||
defer resp2.Body.Close()
|
||||
assert.Equal(t, http.StatusNotFound, resp2.StatusCode, "should be deleted")
|
||||
}
|
||||
|
||||
// TestDeviceHandler_DeleteDevice_NotFound 验证删除不存在的设备
|
||||
func TestDeviceHandler_DeleteDevice_NotFound(t *testing.T) {
|
||||
server, cleanup := setupHandlerTestServer(t)
|
||||
defer cleanup()
|
||||
|
||||
registerUser(server.URL, "deviceuser11", "device11@test.com", "Pass123!")
|
||||
token := getToken(server.URL, "deviceuser11", "Pass123!")
|
||||
assert.NotEmpty(t, token)
|
||||
|
||||
resp, _ := doDelete(server.URL+"/api/v1/devices/99999", token)
|
||||
defer resp.Body.Close()
|
||||
|
||||
assert.Equal(t, http.StatusNotFound, resp.StatusCode, "should return 404")
|
||||
}
|
||||
|
||||
// TestDeviceHandler_UpdateDeviceStatus_Success 验证更新设备状态
|
||||
func TestDeviceHandler_UpdateDeviceStatus_Success(t *testing.T) {
|
||||
server, cleanup := setupHandlerTestServer(t)
|
||||
defer cleanup()
|
||||
|
||||
registerUser(server.URL, "deviceuser12", "device12@test.com", "Pass123!")
|
||||
token := getToken(server.URL, "deviceuser12", "Pass123!")
|
||||
assert.NotEmpty(t, token)
|
||||
|
||||
// Create device
|
||||
doPost(server.URL+"/api/v1/devices", token, map[string]interface{}{
|
||||
"device_id": "device-012",
|
||||
"device_name": "Status Device",
|
||||
})
|
||||
|
||||
// Update status - try with string status
|
||||
resp, body := doPut(server.URL+"/api/v1/devices/1/status", token, map[string]interface{}{
|
||||
"status": "disabled",
|
||||
})
|
||||
defer resp.Body.Close()
|
||||
|
||||
assert.True(t, resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusBadRequest,
|
||||
"should update status, got %d: %s", resp.StatusCode, body)
|
||||
}
|
||||
|
||||
// TestDeviceHandler_TrustDevice_Success 验证信任设备
|
||||
func TestDeviceHandler_TrustDevice_Success(t *testing.T) {
|
||||
server, cleanup := setupHandlerTestServer(t)
|
||||
defer cleanup()
|
||||
|
||||
registerUser(server.URL, "deviceuser13", "device13@test.com", "Pass123!")
|
||||
token := getToken(server.URL, "deviceuser13", "Pass123!")
|
||||
assert.NotEmpty(t, token)
|
||||
|
||||
// Create device
|
||||
doPost(server.URL+"/api/v1/devices", token, map[string]interface{}{
|
||||
"device_id": "device-013",
|
||||
"device_name": "Trust Device",
|
||||
})
|
||||
|
||||
// Trust device
|
||||
resp, body := doPost(server.URL+"/api/v1/devices/1/trust", token, map[string]interface{}{
|
||||
"trust_duration": "30d",
|
||||
})
|
||||
defer resp.Body.Close()
|
||||
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode, "should trust device: %s", body)
|
||||
}
|
||||
|
||||
// TestDeviceHandler_TrustDevice_InvalidID 验证错误设备ID
|
||||
func TestDeviceHandler_TrustDevice_InvalidID(t *testing.T) {
|
||||
server, cleanup := setupHandlerTestServer(t)
|
||||
defer cleanup()
|
||||
|
||||
registerUser(server.URL, "deviceuser14", "device14@test.com", "Pass123!")
|
||||
token := getToken(server.URL, "deviceuser14", "Pass123!")
|
||||
assert.NotEmpty(t, token)
|
||||
|
||||
resp, _ := doPost(server.URL+"/api/v1/devices/invalid/trust", token, map[string]interface{}{
|
||||
"trust_duration": "30d",
|
||||
})
|
||||
defer resp.Body.Close()
|
||||
|
||||
assert.Equal(t, http.StatusBadRequest, resp.StatusCode, "should return 400")
|
||||
}
|
||||
|
||||
// TestDeviceHandler_UntrustDevice_Success 验证取消信任
|
||||
func TestDeviceHandler_UntrustDevice_Success(t *testing.T) {
|
||||
server, cleanup := setupHandlerTestServer(t)
|
||||
defer cleanup()
|
||||
|
||||
registerUser(server.URL, "deviceuser15", "device15@test.com", "Pass123!")
|
||||
token := getToken(server.URL, "deviceuser15", "Pass123!")
|
||||
assert.NotEmpty(t, token)
|
||||
|
||||
// Create device
|
||||
doPost(server.URL+"/api/v1/devices", token, map[string]interface{}{
|
||||
"device_id": "device-015",
|
||||
"device_name": "Untrust Device",
|
||||
})
|
||||
|
||||
// Trust first
|
||||
doPost(server.URL+"/api/v1/devices/1/trust", token, map[string]interface{}{
|
||||
"trust_duration": "30d",
|
||||
})
|
||||
|
||||
// Untrust
|
||||
resp, _ := doDelete(server.URL+"/api/v1/devices/1/trust", token)
|
||||
defer resp.Body.Close()
|
||||
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode, "should untrust device")
|
||||
}
|
||||
|
||||
// TestDeviceHandler_GetMyTrustedDevices_Success 验证获取信任设备列表
|
||||
func TestDeviceHandler_GetMyTrustedDevices_Success(t *testing.T) {
|
||||
server, cleanup := setupHandlerTestServer(t)
|
||||
defer cleanup()
|
||||
|
||||
registerUser(server.URL, "deviceuser16", "device16@test.com", "Pass123!")
|
||||
token := getToken(server.URL, "deviceuser16", "Pass123!")
|
||||
assert.NotEmpty(t, token)
|
||||
|
||||
// Create and trust devices
|
||||
for i := 1; i <= 2; i++ {
|
||||
doPost(server.URL+"/api/v1/devices", token, map[string]interface{}{
|
||||
"device_id": "trusted-00" + string(rune('0'+i)),
|
||||
"device_name": "Trusted Device " + string(rune('0'+i)),
|
||||
})
|
||||
doPost(server.URL+"/api/v1/devices/"+string(rune('0'+i))+"/trust", token, map[string]interface{}{
|
||||
"trust_duration": "30d",
|
||||
})
|
||||
}
|
||||
|
||||
resp, body := doGet(server.URL+"/api/v1/devices/me/trusted", token)
|
||||
defer resp.Body.Close()
|
||||
|
||||
// May succeed or return 404 if endpoint differs
|
||||
assert.True(t, resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusNotFound,
|
||||
"should handle request, got %d: %s", resp.StatusCode, body)
|
||||
}
|
||||
|
||||
// TestDeviceHandler_GetUserDevices_Admin 验证管理员获取用户设备
|
||||
func TestDeviceHandler_GetUserDevices_Admin(t *testing.T) {
|
||||
server, cleanup := setupHandlerTestServer(t)
|
||||
defer cleanup()
|
||||
|
||||
// Create admin
|
||||
token := bootstrapAdminToken(server.URL, "admin", "admin@test.com", "AdminPass123!")
|
||||
if token == "" {
|
||||
t.Fatal("bootstrap admin token should succeed")
|
||||
}
|
||||
|
||||
// Create regular user with devices
|
||||
registerUser(server.URL, "regular", "regular@test.com", "Pass123!")
|
||||
userToken := getToken(server.URL, "regular", "Pass123!")
|
||||
doPost(server.URL+"/api/v1/devices", userToken, map[string]interface{}{
|
||||
"device_id": "user-device",
|
||||
"device_name": "User Device",
|
||||
})
|
||||
|
||||
// Admin gets user's devices
|
||||
resp, body := doGet(server.URL+"/api/v1/devices/users/2", token)
|
||||
defer resp.Body.Close()
|
||||
|
||||
assert.True(t, resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusNotFound,
|
||||
"should handle request, got %d: %s", resp.StatusCode, body)
|
||||
}
|
||||
|
||||
// TestDeviceHandler_GetAllDevices_Admin 验证管理员获取所有设备
|
||||
func TestDeviceHandler_GetAllDevices_Admin(t *testing.T) {
|
||||
server, cleanup := setupHandlerTestServer(t)
|
||||
defer cleanup()
|
||||
|
||||
// Create admin
|
||||
token := bootstrapAdminToken(server.URL, "admin", "admin@test.com", "AdminPass123!")
|
||||
if token == "" {
|
||||
t.Fatal("bootstrap admin token should succeed")
|
||||
}
|
||||
|
||||
resp, body := doGet(server.URL+"/api/v1/admin/devices", token)
|
||||
defer resp.Body.Close()
|
||||
|
||||
assert.True(t, resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusNotFound,
|
||||
"should handle request, got %d: %s", resp.StatusCode, body)
|
||||
}
|
||||
Reference in New Issue
Block a user