diff --git a/cmd/server/tests/device_cap_test.go b/cmd/server/tests/device_cap_test.go new file mode 100644 index 0000000..98298f1 --- /dev/null +++ b/cmd/server/tests/device_cap_test.go @@ -0,0 +1,390 @@ +package main + +import ( + "bytes" + "encoding/json" + "net/http" + "net/http/httptest" + "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) { + ts, db, _, _ := setupTestServer(t) + defer ts.Close() + + // Create test user with admin role + adminToken := loginTestUser(t, ts, db) + createAdminUser(t, ts, adminToken) + + // Login as admin to get admin token + adminUserID := getTestUserID(t, db) + adminToken = getAdminToken(t, ts, adminUserID) + + // Create a test user + userID := createTestUserForMaxDevices(t, ts, 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", ts.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) + assert.Equal(t, tt.expectedBody, body.String(), "expected response body") + }) + } +} + +// TestUpdateUserMaxDevicesValidation tests validation of max_devices parameter +func TestUpdateUserMaxDevicesValidation(t *testing.T) { + ts, db, _, _ := setupTestServer(t) + defer ts.Close() + + // Create admin user and get token + adminToken := loginTestUser(t, ts, db) + createAdminUser(t, ts, adminToken) + adminUserID := getTestUserID(t, db) + adminToken = getAdminToken(t, ts, adminUserID) + + // Create test user + userID := createTestUserForMaxDevices(t, ts, 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", ts.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) { + ts, db, _, _ := setupTestServer(t) + defer ts.Close() + + // Create admin user + adminToken := loginTestUser(t, ts, db) + createAdminUser(t, ts, adminToken) + adminUserID := getTestUserID(t, db) + adminToken = getAdminToken(t, ts, adminUserID) + + // Create regular user + userID := createTestUserForMaxDevices(t, ts, adminToken) + + t.Run("No authorization", func(t *testing.T) { + payload := map[string]interface{}{ + "max_devices": 10, + } + jsonData, _ := json.Marshal(payload) + + req, _ := http.NewRequest("PUT", ts.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, ts, adminToken) + regularToken := loginTestUserByCredentials(t, ts, "regularuser@example.com", "Test@Pass123!") + + payload := map[string]interface{}{ + "max_devices": 10, + } + jsonData, _ := json.Marshal(payload) + + req, _ := http.NewRequest("PUT", ts.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) { + ts, db, _, _ := setupTestServer(t) + defer ts.Close() + + // Create admin user + adminToken := loginTestUser(t, ts, db) + createAdminUser(t, ts, adminToken) + adminUserID := getTestUserID(t, db) + adminToken = getAdminToken(t, ts, 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", ts.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) { + ts, db, _, _ := setupTestServer(t) + defer ts.Close() + + // Create admin user + adminToken := loginTestUser(t, ts, db) + createAdminUser(t, ts, adminToken) + adminUserID := getTestUserID(t, db) + adminToken = getAdminToken(t, ts, adminUserID) + + payload := map[string]interface{}{ + "max_devices": 10, + } + jsonData, _ := json.Marshal(payload) + + // Missing user ID in URL + req, _ := http.NewRequest("PUT", ts.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) { + ts, db, _, _ := setupTestServer(t) + defer ts.Close() + + // Create admin user + adminToken := loginTestUser(t, ts, db) + createAdminUser(t, ts, adminToken) + adminUserID := getTestUserID(t, db) + adminToken = getAdminToken(t, ts, adminUserID) + + req, _ := http.NewRequest("GET", ts.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 field is 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") + } +} + +// 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() + + var result map[string]interface{} + json.NewDecoder(resp.Body).Decode(&result) + + 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 { + // Now login as admin + loginPayload := map[string]interface{}{ + "login": "admin@example.com", + "password": "Admin@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) + + token := result["access_token"].(string) + return token +} + +// 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) + + return result["access_token"].(string) +}