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 "" }