package handler_test import ( "bytes" "encoding/json" "fmt" "net/http" "testing" ) func TestDeviceHandler_ListDevices(t *testing.T) { server, cleanup := setupHandlerTestServer(t) defer cleanup() registerUser(server.URL, "devicelistuser", "devicelist@test.com", "UserPass123!") token := getToken(server.URL, "devicelistuser", "UserPass123!") resp, body := doGet(server.URL+"/api/v1/devices", token) defer resp.Body.Close() if resp.StatusCode != http.StatusOK { t.Fatalf("expected status %d, got %d, body: %s", http.StatusOK, resp.StatusCode, body) } var result map[string]interface{} if err := json.Unmarshal([]byte(body), &result); err != nil { t.Fatalf("failed to parse response: %v", err) } if result["code"] != float64(0) { t.Errorf("expected code 0, got %v", result["code"]) } } func TestDeviceHandler_ListDevices_Unauthorized(t *testing.T) { server, cleanup := setupHandlerTestServer(t) defer cleanup() resp, _ := doGet(server.URL+"/api/v1/devices", "") defer resp.Body.Close() if resp.StatusCode != http.StatusUnauthorized { t.Errorf("expected status %d, got %d", http.StatusUnauthorized, resp.StatusCode) } } func TestDeviceHandler_CreateDevice(t *testing.T) { server, cleanup := setupHandlerTestServer(t) defer cleanup() registerUser(server.URL, "devicecreateuser", "devicecreate@test.com", "UserPass123!") token := getToken(server.URL, "devicecreateuser", "UserPass123!") resp, body := doPost(server.URL+"/api/v1/devices", token, map[string]interface{}{ "name": "Test Device", "device_id": "device-test-001", "device_type": 3, "device_os": "Windows 10", }) defer resp.Body.Close() if resp.StatusCode != http.StatusCreated { t.Fatalf("expected status %d, got %d, body: %s", http.StatusCreated, resp.StatusCode, body) } var result map[string]interface{} if err := json.Unmarshal([]byte(body), &result); err != nil { t.Fatalf("failed to parse response: %v", err) } if result["code"] != float64(0) { t.Errorf("expected code 0, got %v", result["code"]) } } func TestDeviceHandler_CreateDevice_InvalidBody(t *testing.T) { server, cleanup := setupHandlerTestServer(t) defer cleanup() registerUser(server.URL, "devicecreatebad", "devicecreatebad@test.com", "UserPass123!") token := getToken(server.URL, "devicecreatebad", "UserPass123!") req, _ := http.NewRequest("POST", server.URL+"/api/v1/devices", bytes.NewReader([]byte("not json"))) req.Header.Set("Authorization", "Bearer "+token) req.Header.Set("Content-Type", "application/json") client := &http.Client{} resp, err := client.Do(req) if err != nil { t.Fatalf("request failed: %v", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusBadRequest { t.Errorf("expected status %d for invalid body, got %d", http.StatusBadRequest, resp.StatusCode) } } func TestDeviceHandler_GetDevice(t *testing.T) { server, cleanup := setupHandlerTestServer(t) defer cleanup() registerUser(server.URL, "devicegetuser", "deviceget@test.com", "UserPass123!") token := getToken(server.URL, "devicegetuser", "UserPass123!") deviceID := createDeviceForHandlerTest(t, server.URL, token, "device-get-001", "Get Device") resp, body := doGet(fmt.Sprintf("%s/api/v1/devices/%d", server.URL, deviceID), token) defer resp.Body.Close() if resp.StatusCode != http.StatusOK { t.Fatalf("expected status %d, got %d, body: %s", http.StatusOK, resp.StatusCode, body) } var result map[string]interface{} if err := json.Unmarshal([]byte(body), &result); err != nil { t.Fatalf("failed to parse response: %v", err) } if result["code"] != float64(0) { t.Errorf("expected code 0, got %v", result["code"]) } } func TestDeviceHandler_GetDevice_NotFound(t *testing.T) { server, cleanup := setupHandlerTestServer(t) defer cleanup() registerUser(server.URL, "devicegetnf", "devicegetnf@test.com", "UserPass123!") token := getToken(server.URL, "devicegetnf", "UserPass123!") resp, body := doGet(server.URL+"/api/v1/devices/99999", token) defer resp.Body.Close() if resp.StatusCode != http.StatusNotFound { t.Errorf("expected status %d, got %d, body: %s", http.StatusNotFound, resp.StatusCode, body) } } func TestDeviceHandler_GetDevice_InvalidID(t *testing.T) { server, cleanup := setupHandlerTestServer(t) defer cleanup() registerUser(server.URL, "devicegetinv", "devicegetinv@test.com", "UserPass123!") token := getToken(server.URL, "devicegetinv", "UserPass123!") resp, body := doGet(server.URL+"/api/v1/devices/invalid", token) defer resp.Body.Close() if resp.StatusCode != http.StatusBadRequest { t.Errorf("expected status %d, got %d, body: %s", http.StatusBadRequest, resp.StatusCode, body) } } func TestDeviceHandler_UpdateDevice(t *testing.T) { server, cleanup := setupHandlerTestServer(t) defer cleanup() registerUser(server.URL, "deviceupdateuser", "deviceupdate@test.com", "UserPass123!") token := getToken(server.URL, "deviceupdateuser", "UserPass123!") deviceID := createDeviceForHandlerTest(t, server.URL, token, "device-update-001", "Original Name") resp, body := doPut(fmt.Sprintf("%s/api/v1/devices/%d", server.URL, deviceID), token, map[string]interface{}{ "device_name": "Updated Name", }) defer resp.Body.Close() if resp.StatusCode != http.StatusOK { t.Fatalf("expected status %d, got %d, body: %s", http.StatusOK, resp.StatusCode, body) } var result map[string]interface{} if err := json.Unmarshal([]byte(body), &result); err != nil { t.Fatalf("failed to parse response: %v", err) } if result["code"] != float64(0) { t.Errorf("expected code 0, got %v", result["code"]) } } func TestDeviceHandler_UpdateDevice_NotFound(t *testing.T) { server, cleanup := setupHandlerTestServer(t) defer cleanup() registerUser(server.URL, "deviceupdatenf", "deviceupdatenf@test.com", "UserPass123!") token := getToken(server.URL, "deviceupdatenf", "UserPass123!") resp, body := doPut(server.URL+"/api/v1/devices/99999", token, map[string]interface{}{ "device_name": "Updated Name", }) defer resp.Body.Close() if resp.StatusCode != http.StatusNotFound { t.Errorf("expected status %d, got %d, body: %s", http.StatusNotFound, resp.StatusCode, body) } } func TestDeviceHandler_DeleteDevice(t *testing.T) { server, cleanup := setupHandlerTestServer(t) defer cleanup() registerUser(server.URL, "devicedeluser", "devicedel@test.com", "UserPass123!") token := getToken(server.URL, "devicedeluser", "UserPass123!") deviceID := createDeviceForHandlerTest(t, server.URL, token, "device-del-001", "Delete Device") resp, body := doDelete(fmt.Sprintf("%s/api/v1/devices/%d", server.URL, deviceID), token) defer resp.Body.Close() if resp.StatusCode != http.StatusOK { t.Fatalf("expected status %d, got %d, body: %s", http.StatusOK, resp.StatusCode, body) } // Verify deletion getResp, _ := doGet(fmt.Sprintf("%s/api/v1/devices/%d", server.URL, deviceID), token) defer getResp.Body.Close() if getResp.StatusCode != http.StatusNotFound { t.Errorf("expected device to be deleted, got status %d", getResp.StatusCode) } } func TestDeviceHandler_DeleteDevice_NotFound(t *testing.T) { server, cleanup := setupHandlerTestServer(t) defer cleanup() registerUser(server.URL, "devicedelnf", "devicedelnf@test.com", "UserPass123!") token := getToken(server.URL, "devicedelnf", "UserPass123!") resp, body := doDelete(server.URL+"/api/v1/devices/99999", token) defer resp.Body.Close() if resp.StatusCode != http.StatusNotFound { t.Errorf("expected status %d, got %d, body: %s", http.StatusNotFound, resp.StatusCode, body) } } func TestDeviceHandler_UpdateDeviceStatus(t *testing.T) { server, cleanup := setupHandlerTestServer(t) defer cleanup() registerUser(server.URL, "devicestatususer", "devicestatus@test.com", "UserPass123!") token := getToken(server.URL, "devicestatususer", "UserPass123!") deviceID := createDeviceForHandlerTest(t, server.URL, token, "device-status-001", "Status Device") resp, body := doPut(fmt.Sprintf("%s/api/v1/devices/%d/status", server.URL, deviceID), token, map[string]interface{}{ "status": "inactive", }) defer resp.Body.Close() if resp.StatusCode != http.StatusOK { t.Fatalf("expected status %d, got %d, body: %s", http.StatusOK, resp.StatusCode, body) } } func TestDeviceHandler_UpdateDeviceStatus_InvalidStatus(t *testing.T) { server, cleanup := setupHandlerTestServer(t) defer cleanup() registerUser(server.URL, "devicestatusinv", "devicestatusinv@test.com", "UserPass123!") token := getToken(server.URL, "devicestatusinv", "UserPass123!") deviceID := createDeviceForHandlerTest(t, server.URL, token, "device-status-inv-001", "Status Device") resp, body := doPut(fmt.Sprintf("%s/api/v1/devices/%d/status", server.URL, deviceID), token, map[string]interface{}{ "status": "invalid_status", }) defer resp.Body.Close() if resp.StatusCode != http.StatusBadRequest { t.Errorf("expected status %d, got %d, body: %s", http.StatusBadRequest, resp.StatusCode, body) } } func TestDeviceHandler_TrustDevice(t *testing.T) { server, cleanup := setupHandlerTestServer(t) defer cleanup() registerUser(server.URL, "devicetrustuser", "devicetrust@test.com", "UserPass123!") token := getToken(server.URL, "devicetrustuser", "UserPass123!") deviceID := createDeviceForHandlerTest(t, server.URL, token, "device-trust-001", "Trust Device") resp, body := doPost(fmt.Sprintf("%s/api/v1/devices/%d/trust", server.URL, deviceID), token, map[string]interface{}{ "trust_duration": "24h", }) defer resp.Body.Close() if resp.StatusCode != http.StatusOK { t.Fatalf("expected status %d, got %d, body: %s", http.StatusOK, resp.StatusCode, body) } } func TestDeviceHandler_UntrustDevice(t *testing.T) { server, cleanup := setupHandlerTestServer(t) defer cleanup() registerUser(server.URL, "deviceuntrustuser", "deviceuntrust@test.com", "UserPass123!") token := getToken(server.URL, "deviceuntrustuser", "UserPass123!") deviceID := createDeviceForHandlerTest(t, server.URL, token, "device-untrust-001", "Untrust Device") // First trust the device trustResp, trustBody := doPost(fmt.Sprintf("%s/api/v1/devices/%d/trust", server.URL, deviceID), token, map[string]interface{}{ "trust_duration": "24h", }) defer trustResp.Body.Close() if trustResp.StatusCode != http.StatusOK { t.Fatalf("expected trust status %d, got %d, body: %s", http.StatusOK, trustResp.StatusCode, trustBody) } // Then untrust resp, body := doDelete(fmt.Sprintf("%s/api/v1/devices/%d/trust", server.URL, deviceID), token) defer resp.Body.Close() if resp.StatusCode != http.StatusOK { t.Fatalf("expected status %d, got %d, body: %s", http.StatusOK, resp.StatusCode, body) } } func TestDeviceHandler_GetMyTrustedDevices(t *testing.T) { server, cleanup := setupHandlerTestServer(t) defer cleanup() registerUser(server.URL, "devicetrusteduser", "devicetrusted@test.com", "UserPass123!") token := getToken(server.URL, "devicetrusteduser", "UserPass123!") deviceID := createDeviceForHandlerTest(t, server.URL, token, "device-trusted-001", "Trusted Device") // Trust the device first trustResp, trustBody := doPost(fmt.Sprintf("%s/api/v1/devices/%d/trust", server.URL, deviceID), token, map[string]interface{}{ "trust_duration": "24h", }) defer trustResp.Body.Close() if trustResp.StatusCode != http.StatusOK { t.Fatalf("expected trust status %d, got %d, body: %s", http.StatusOK, trustResp.StatusCode, trustBody) } resp, body := doGet(server.URL+"/api/v1/devices/me/trusted", token) defer resp.Body.Close() if resp.StatusCode != http.StatusOK { t.Fatalf("expected status %d, got %d, body: %s", http.StatusOK, resp.StatusCode, body) } var result map[string]interface{} if err := json.Unmarshal([]byte(body), &result); err != nil { t.Fatalf("failed to parse response: %v", err) } if result["code"] != float64(0) { t.Errorf("expected code 0, got %v", result["code"]) } } func TestDeviceHandler_LogoutAllOtherDevices(t *testing.T) { server, cleanup := setupHandlerTestServer(t) defer cleanup() registerUser(server.URL, "devicelogoutuser", "devicelogout@test.com", "UserPass123!") token := getToken(server.URL, "devicelogoutuser", "UserPass123!") deviceID := createDeviceForHandlerTest(t, server.URL, token, "device-logout-001", "Logout Device") req, _ := http.NewRequest("POST", server.URL+"/api/v1/devices/me/logout-others", nil) req.Header.Set("Authorization", "Bearer "+token) req.Header.Set("X-Device-ID", fmt.Sprintf("%d", deviceID)) client := &http.Client{} resp, err := client.Do(req) if err != nil { t.Fatalf("request failed: %v", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { bodyBytes, _ := json.Marshal(resp.Body) t.Fatalf("expected status %d, got %d, body: %s", http.StatusOK, resp.StatusCode, string(bodyBytes)) } } func TestDeviceHandler_LogoutAllOtherDevices_MissingDeviceID(t *testing.T) { server, cleanup := setupHandlerTestServer(t) defer cleanup() registerUser(server.URL, "devicelogoutbad", "devicelogoutbad@test.com", "UserPass123!") token := getToken(server.URL, "devicelogoutbad", "UserPass123!") resp, body := doPost(server.URL+"/api/v1/devices/me/logout-others", token, nil) defer resp.Body.Close() if resp.StatusCode != http.StatusBadRequest { t.Errorf("expected status %d, got %d, body: %s", http.StatusBadRequest, resp.StatusCode, body) } } func TestDeviceHandler_GetUserDevices_AdminCanViewOthers(t *testing.T) { server, cleanup := setupHandlerTestServer(t) defer cleanup() t.Setenv("BOOTSTRAP_SECRET", "handler-bootstrap-secret") adminToken := bootstrapAdmin(server.URL, "handler-bootstrap-secret", "deviceadmin", "deviceadmin@test.com", "AdminPass123!") registerUser(server.URL, "deviceuserview", "deviceuserview@test.com", "UserPass123!") if adminToken == "" { t.Fatal("bootstrap admin should return access token") } resp, body := doGet(server.URL+"/api/v1/devices/users/2", adminToken) defer resp.Body.Close() if resp.StatusCode != http.StatusOK { t.Fatalf("expected status %d, got %d, body: %s", http.StatusOK, resp.StatusCode, body) } } func TestDeviceHandler_GetUserDevices_NonAdminForbidden(t *testing.T) { server, cleanup := setupHandlerTestServer(t) defer cleanup() registerUser(server.URL, "deviceuser1", "deviceuser1@test.com", "UserPass123!") registerUser(server.URL, "deviceuser2", "deviceuser2@test.com", "UserPass123!") token := getToken(server.URL, "deviceuser1", "UserPass123!") resp, body := doGet(server.URL+"/api/v1/devices/users/2", token) defer resp.Body.Close() if resp.StatusCode != http.StatusForbidden { t.Errorf("expected status %d, got %d, body: %s", http.StatusForbidden, resp.StatusCode, body) } } func TestDeviceHandler_GetAllDevices_AdminOnly(t *testing.T) { server, cleanup := setupHandlerTestServer(t) defer cleanup() t.Setenv("BOOTSTRAP_SECRET", "handler-bootstrap-secret") adminToken := bootstrapAdmin(server.URL, "handler-bootstrap-secret", "deviceadmin2", "deviceadmin2@test.com", "AdminPass123!") if adminToken == "" { t.Fatal("bootstrap admin should return access token") } resp, body := doGet(server.URL+"/api/v1/admin/devices", adminToken) defer resp.Body.Close() if resp.StatusCode != http.StatusOK { t.Fatalf("expected status %d, got %d, body: %s", http.StatusOK, resp.StatusCode, body) } } func TestDeviceHandler_GetAllDevices_NonAdminForbidden(t *testing.T) { server, cleanup := setupHandlerTestServer(t) defer cleanup() registerUser(server.URL, "deviceuser3", "deviceuser3@test.com", "UserPass123!") token := getToken(server.URL, "deviceuser3", "UserPass123!") resp, body := doGet(server.URL+"/api/v1/admin/devices", token) defer resp.Body.Close() if resp.StatusCode != http.StatusForbidden { t.Errorf("expected status %d, got %d, body: %s", http.StatusForbidden, resp.StatusCode, body) } } func TestDeviceHandler_TrustDeviceByDeviceID(t *testing.T) { server, cleanup := setupHandlerTestServer(t) defer cleanup() registerUser(server.URL, "devicetrustiduser", "devicetrustid@test.com", "UserPass123!") token := getToken(server.URL, "devicetrustiduser", "UserPass123!") // Create device with specific device_id resp, body := doPost(server.URL+"/api/v1/devices", token, map[string]interface{}{ "name": "Trust By ID Device", "device_id": "my-unique-device-id", "device_type": 1, }) defer resp.Body.Close() if resp.StatusCode != http.StatusCreated { t.Fatalf("expected create status %d, got %d, body: %s", http.StatusCreated, resp.StatusCode, body) } // Trust by device ID trustResp, trustBody := doPost(server.URL+"/api/v1/devices/by-device-id/my-unique-device-id/trust", token, map[string]interface{}{ "trust_duration": "24h", }) defer trustResp.Body.Close() if trustResp.StatusCode != http.StatusOK { t.Fatalf("expected status %d, got %d, body: %s", http.StatusOK, trustResp.StatusCode, trustBody) } } func TestDeviceHandler_TrustDeviceByDeviceID_EmptyID(t *testing.T) { server, cleanup := setupHandlerTestServer(t) defer cleanup() registerUser(server.URL, "devicetrustidbad", "devicetrustidbad@test.com", "UserPass123!") token := getToken(server.URL, "devicetrustidbad", "UserPass123!") // The route uses ":deviceId" path param, so empty ID would be a different route or 404 // Actually the route is /by-device-id/:deviceId/trust, so empty deviceId is not matched // Let's test with a device ID that doesn't exist resp, body := doPost(server.URL+"/api/v1/devices/by-device-id/nonexistent/trust", token, map[string]interface{}{ "trust_duration": "24h", }) defer resp.Body.Close() // Service returns error for non-existent device if resp.StatusCode != http.StatusNotFound && resp.StatusCode != http.StatusInternalServerError { t.Errorf("expected status 404 or 500 for non-existent device, got %d, body: %s", resp.StatusCode, body) } }