Files
bookhoard/cmd/server/tests/device_cap_test.go
T
john-okeefe b2a3955c1e fix(tests): complete TestServerSetup migration for remaining test files
Finish migrating all test files to the new TestServerSetup pattern
introduced by the goroutine cleanup refactoring. This resolves all
remaining compilation errors in the test suite.

Changes:
- device_cap_test.go: Fix undefined ts references (7 instances)
  * Replace ts.URL with setup.Server.URL in all test functions
  * Fix URL references in t.Run subtest closures

- queue_test.go: Fix undefined db and helper function issues (5 instances)
  * Replace db.CreateDevice with setup.DB.CreateDevice
  * Fix loginAdminUser() to use ts/db parameters instead of setup
  * Fix loginUserWithID() to use ts parameter instead of setup

- websocket_test.go: Convert 5 tests to new TestServerSetup pattern
  * Replace old pattern (ts, queries, _) with new pattern (setup)
  * Update all resource references to use setup.Server and setup.DB
  * Fix getTestUserID calls to include t parameter

Build Impact:
- All compilation errors resolved
- Integration tests now compile successfully
- No functional changes to test logic

Related: TestServerSetup cleanup pattern (TEST_CLEANUP_PATTERN.md)
2026-02-10 13:24:08 -05:00

470 lines
14 KiB
Go

package main
import (
"bytes"
"encoding/base64"
"encoding/json"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// TestUpdateUserMaxDevices tests the admin endpoint for updating user device cap
func TestUpdateUserMaxDevices(t *testing.T) {
setup := setupTestServer(t)
// Create test user with admin role
loginTestUser(t, setup.Server, setup.DB)
adminUserID := getTestUserID(t, setup.DB)
adminToken := getAdminToken(t, setup.Server, adminUserID)
// Create a test user
userID := createTestUserForMaxDevices(t, setup.Server, adminToken)
tests := []struct {
name string
maxDevices int32
expectedStatus int
expectedBody string
}{
{
name: "Update to 5 devices",
maxDevices: 5,
expectedStatus: http.StatusOK,
expectedBody: `{"message":"max devices updated"}`,
},
{
name: "Update to 10 devices (default)",
maxDevices: 10,
expectedStatus: http.StatusOK,
expectedBody: `{"message":"max devices updated"}`,
},
{
name: "Update to 50 devices",
maxDevices: 50,
expectedStatus: http.StatusOK,
expectedBody: `{"message":"max devices updated"}`,
},
{
name: "Update to 100 devices (maximum)",
maxDevices: 100,
expectedStatus: http.StatusOK,
expectedBody: `{"message":"max devices updated"}`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
payload := map[string]interface{}{
"max_devices": tt.maxDevices,
}
jsonData, _ := json.Marshal(payload)
req, _ := http.NewRequest("PUT", setup.Server.URL+"/api/auth/users/"+userID.String()+"/max-devices", bytes.NewBuffer(jsonData))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+adminToken)
client := &http.Client{}
resp, err := client.Do(req)
require.NoError(t, err)
defer resp.Body.Close()
assert.Equal(t, tt.expectedStatus, resp.StatusCode, "expected status code")
body := new(bytes.Buffer)
body.ReadFrom(resp.Body)
// Trim trailing whitespace/newline from response body
assert.Equal(t, tt.expectedBody, strings.TrimSpace(body.String()), "expected response body")
})
}
}
// TestUpdateUserMaxDevicesValidation tests validation of max_devices parameter
func TestUpdateUserMaxDevicesValidation(t *testing.T) {
setup := setupTestServer(t)
// Create admin user and get token
adminToken := loginTestUser(t, setup.Server, setup.DB)
createAdminUser(t, setup.Server, adminToken)
adminUserID := getTestUserID(t, setup.DB)
adminToken = getAdminToken(t, setup.Server, adminUserID)
// Create test user
userID := createTestUserForMaxDevices(t, setup.Server, adminToken)
tests := []struct {
name string
maxDevices int32
expectedStatus int
}{
{
name: "Zero devices (below minimum)",
maxDevices: 0,
expectedStatus: http.StatusBadRequest,
},
{
name: "Negative devices",
maxDevices: -1,
expectedStatus: http.StatusBadRequest,
},
{
name: "101 devices (above maximum)",
maxDevices: 101,
expectedStatus: http.StatusBadRequest,
},
{
name: "1000 devices (far above maximum)",
maxDevices: 1000,
expectedStatus: http.StatusBadRequest,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
payload := map[string]interface{}{
"max_devices": tt.maxDevices,
}
jsonData, _ := json.Marshal(payload)
req, _ := http.NewRequest("PUT", setup.Server.URL+"/api/auth/users/"+userID.String()+"/max-devices", bytes.NewBuffer(jsonData))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+adminToken)
client := &http.Client{}
resp, err := client.Do(req)
require.NoError(t, err)
defer resp.Body.Close()
assert.Equal(t, tt.expectedStatus, resp.StatusCode, "expected validation error")
})
}
}
// TestUpdateUserMaxDevicesAuth tests authentication requirements
func TestUpdateUserMaxDevicesAuth(t *testing.T) {
setup := setupTestServer(t)
// Create admin user
adminToken := loginTestUser(t, setup.Server, setup.DB)
createAdminUser(t, setup.Server, adminToken)
adminUserID := getTestUserID(t, setup.DB)
adminToken = getAdminToken(t, setup.Server, adminUserID)
// Create regular user
userID := createTestUserForMaxDevices(t, setup.Server, adminToken)
t.Run("No authorization", func(t *testing.T) {
payload := map[string]interface{}{
"max_devices": 10,
}
jsonData, _ := json.Marshal(payload)
req, _ := http.NewRequest("PUT", setup.Server.URL+"/api/auth/users/"+userID.String()+"/max-devices", bytes.NewBuffer(jsonData))
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
require.NoError(t, err)
defer resp.Body.Close()
assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
})
t.Run("Non-admin user", func(t *testing.T) {
// Create another regular user and get their token
_ = createTestUserForMaxDevices(t, setup.Server, adminToken)
// Login as the maxdevices user (who is a regular user, not admin)
regularToken := loginTestUserByCredentials(t, setup.Server, "maxdevices@example.com", "Test@Pass123!")
require.NotEmpty(t, regularToken, "Failed to login as regular user")
payload := map[string]interface{}{
"max_devices": 10,
}
jsonData, _ := json.Marshal(payload)
req, _ := http.NewRequest("PUT", setup.Server.URL+"/api/auth/users/"+userID.String()+"/max-devices", bytes.NewBuffer(jsonData))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+regularToken)
client := &http.Client{}
resp, err := client.Do(req)
require.NoError(t, err)
defer resp.Body.Close()
assert.Equal(t, http.StatusForbidden, resp.StatusCode)
})
}
// TestUpdateUserMaxDevicesNonExistentUser tests with non-existent user ID
func TestUpdateUserMaxDevicesNonExistentUser(t *testing.T) {
setup := setupTestServer(t)
// Create admin user
adminToken := loginTestUser(t, setup.Server, setup.DB)
createAdminUser(t, setup.Server, adminToken)
adminUserID := getTestUserID(t, setup.DB)
adminToken = getAdminToken(t, setup.Server, adminUserID)
// Use a non-existent user ID
nonExistentUserID := uuid.New()
payload := map[string]interface{}{
"max_devices": 10,
}
jsonData, _ := json.Marshal(payload)
req, _ := http.NewRequest("PUT", setup.Server.URL+"/api/auth/users/"+nonExistentUserID.String()+"/max-devices", bytes.NewBuffer(jsonData))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+adminToken)
client := &http.Client{}
resp, err := client.Do(req)
require.NoError(t, err)
defer resp.Body.Close()
// Should return 500 or 404 depending on implementation
assert.True(t, resp.StatusCode == http.StatusInternalServerError || resp.StatusCode == http.StatusNotFound)
}
// TestUpdateUserMaxDevicesMissingUserID tests with missing user ID in URL
func TestUpdateUserMaxDevicesMissingUserID(t *testing.T) {
setup := setupTestServer(t)
// Create admin user
adminToken := loginTestUser(t, setup.Server, setup.DB)
createAdminUser(t, setup.Server, adminToken)
adminUserID := getTestUserID(t, setup.DB)
adminToken = getAdminToken(t, setup.Server, adminUserID)
payload := map[string]interface{}{
"max_devices": 10,
}
jsonData, _ := json.Marshal(payload)
// Missing user ID in URL
req, _ := http.NewRequest("PUT", setup.Server.URL+"/api/auth/users//max-devices", bytes.NewBuffer(jsonData))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+adminToken)
client := &http.Client{}
resp, err := client.Do(req)
require.NoError(t, err)
defer resp.Body.Close()
assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
}
// TestListUsersIncludesMaxDevices tests that List Users returns max_devices field
func TestListUsersIncludesMaxDevices(t *testing.T) {
setup := setupTestServer(t)
// Create admin user
adminToken := loginTestUser(t, setup.Server, setup.DB)
createAdminUser(t, setup.Server, adminToken)
adminUserID := getTestUserID(t, setup.DB)
adminToken = getAdminToken(t, setup.Server, adminUserID)
req, _ := http.NewRequest("GET", setup.Server.URL+"/api/auth/users", nil)
req.Header.Set("Authorization", "Bearer "+adminToken)
client := &http.Client{}
resp, err := client.Do(req)
require.NoError(t, err)
defer resp.Body.Close()
assert.Equal(t, http.StatusOK, resp.StatusCode)
var users []map[string]interface{}
json.NewDecoder(resp.Body).Decode(&users)
// Verify max_devices and device_count fields are present in response
if len(users) > 0 {
_, hasMaxDevices := users[0]["max_devices"]
assert.True(t, hasMaxDevices, "max_devices field should be present in user list")
_, hasDeviceCount := users[0]["device_count"]
assert.True(t, hasDeviceCount, "device_count field should be present in user list")
}
}
// Helper function to create admin user
func createAdminUser(t *testing.T, ts *httptest.Server, token string) {
createUserPayload := map[string]interface{}{
"email": "admin@example.com",
"username": "adminuser",
"password": "Admin@Pass123!",
"first_name": "Admin",
"last_name": "User",
"role": "admin",
}
jsonData, _ := json.Marshal(createUserPayload)
req, _ := http.NewRequest("POST", ts.URL+"/api/auth/register", bytes.NewBuffer(jsonData))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+token)
client := &http.Client{}
resp, _ := client.Do(req)
resp.Body.Close()
}
// Helper function to create test user for max devices tests
func createTestUserForMaxDevices(t *testing.T, ts *httptest.Server, adminToken string) uuid.UUID {
createUserPayload := map[string]interface{}{
"email": "maxdevices@example.com",
"username": "maxdevicesuser",
"password": "Test@Pass123!",
"first_name": "Test",
"last_name": "User",
}
jsonData, _ := json.Marshal(createUserPayload)
req, _ := http.NewRequest("POST", ts.URL+"/api/auth/register", bytes.NewBuffer(jsonData))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+adminToken)
client := &http.Client{}
resp, err := client.Do(req)
require.NoError(t, err)
defer resp.Body.Close()
// Check if user creation succeeded or already exists (409 Conflict)
if resp.StatusCode == http.StatusConflict {
// User already exists, try to login to get their access token
t.Logf("User maxdevices@example.com already exists, logging in to get ID")
loginPayload := map[string]interface{}{
"login": "maxdevices@example.com",
"password": "Test@Pass123!",
}
loginData, _ := json.Marshal(loginPayload)
loginReq, _ := http.NewRequest("POST", ts.URL+"/api/auth/login", bytes.NewBuffer(loginData))
loginReq.Header.Set("Content-Type", "application/json")
loginResp, err := client.Do(loginReq)
require.NoError(t, err)
defer loginResp.Body.Close()
var loginResult map[string]interface{}
json.NewDecoder(loginResp.Body).Decode(&loginResult)
// Extract user_id from JWT or response
// The access_token contains the user ID in the JWT claims
if accessToken, ok := loginResult["access_token"].(string); ok {
// Simple JWT parsing to get user_id
// JWT format: header.payload.signature
parts := strings.Split(accessToken, ".")
if len(parts) >= 2 {
// Decode payload (base64url)
payload := parts[1]
// Add padding if needed
for len(payload)%4 != 0 {
payload += "="
}
// Decode base64
decodedBytes, err := base64.StdEncoding.DecodeString(payload)
if err == nil {
var claims map[string]interface{}
if err := json.Unmarshal(decodedBytes, &claims); err == nil {
t.Logf("JWT claims: %+v", claims)
if userIDStr, ok := claims["user_id"].(string); ok {
userID, _ := uuid.Parse(userIDStr)
t.Logf("Extracted userID from JWT: %s", userID)
return userID
} else {
t.Logf("user_id not found in JWT claims")
}
} else {
t.Logf("Failed to unmarshal JWT claims: %v", err)
}
} else {
t.Logf("Failed to decode base64: %v", err)
}
} else {
t.Logf("JWT doesn't have enough parts: %d", len(parts))
}
} else {
t.Logf("access_token not found in login result")
}
// If JWT parsing fails, return empty UUID
return uuid.UUID{}
}
var result map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
// Check if user creation was successful
if result["user"] == nil {
// User creation failed for another reason
t.Logf("User creation failed, response: %+v", result)
return uuid.UUID{}
}
userIDStr := result["user"].(map[string]interface{})["id"].(string)
userID, _ := uuid.Parse(userIDStr)
return userID
}
// Helper function to get admin token
func getAdminToken(t *testing.T, ts *httptest.Server, userID uuid.UUID) string {
// First, try to login as the admin user (testuser who was created with admin role)
loginPayload := map[string]interface{}{
"login": "testuser@example.com",
"password": "Test@Pass123!",
}
jsonData, _ := json.Marshal(loginPayload)
req, _ := http.NewRequest("POST", ts.URL+"/api/auth/login", bytes.NewBuffer(jsonData))
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
require.NoError(t, err)
defer resp.Body.Close()
var result map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
// Safe type assertion with check
if accessToken, ok := result["access_token"].(string); ok {
return accessToken
}
// Handle error case - if login failed, return empty string
return ""
}
// Helper function to login user by credentials
func loginTestUserByCredentials(t *testing.T, ts *httptest.Server, email, password string) string {
loginPayload := map[string]interface{}{
"login": email,
"password": password,
}
jsonData, _ := json.Marshal(loginPayload)
req, _ := http.NewRequest("POST", ts.URL+"/api/auth/login", bytes.NewBuffer(jsonData))
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
require.NoError(t, err)
defer resp.Body.Close()
var result map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
// Safe type assertion with check
if accessToken, ok := result["access_token"].(string); ok {
return accessToken
}
// Login failed - return empty string
return ""
}