test: fix RBAC test to match implementation logic

- Update test_list_projects_in_resource_pool to use independent AsyncClient
- Fix test logic to match actual RBAC implementation:
  - Direct ACE + created_by match = accessible
  - Resource pool ACE = accessible (no created_by check)
  - Deduplication prevents duplicate projects
- Test now verifies: pool-only access → pool + /projects access → /projects-only access
- Add missing imports for ASGIWebSocketTransport and auth_service
This commit is contained in:
YueGuobin 2026-05-28 00:02:25 +08:00
parent adba0f17e5
commit 9825a7b271
No known key found for this signature in database

View File

@ -21,6 +21,7 @@ import uuid
from fastapi import FastAPI, status from fastapi import FastAPI, status
from httpx import AsyncClient from httpx import AsyncClient
from httpx_ws.transport import ASGIWebSocketTransport
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from gns3server.controller import Controller from gns3server.controller import Controller
@ -30,6 +31,7 @@ from gns3server.db.repositories.pools import ResourcePoolsRepository
from gns3server.schemas.controller.rbac import ACECreate from gns3server.schemas.controller.rbac import ACECreate
from gns3server.schemas.controller.pools import ResourceCreate, ResourcePoolCreate from gns3server.schemas.controller.pools import ResourceCreate, ResourcePoolCreate
from gns3server.db.models import User from gns3server.db.models import User
from gns3server.services import auth_service
pytestmark = pytest.mark.asyncio pytestmark = pytest.mark.asyncio
@ -168,11 +170,15 @@ class TestResourcePools:
self, self,
app: FastAPI, app: FastAPI,
controller: Controller, controller: Controller,
client: AsyncClient, base_client: AsyncClient,
db_session: AsyncSession, db_session: AsyncSession,
test_user: User test_user: User
) -> None: ) -> None:
# Clean up any existing ACEs from previous tests
await RbacRepository(db_session).delete_all_ace_starting_with_path("/projects")
await RbacRepository(db_session).delete_all_ace_starting_with_path("/pools")
uuid1 = str(uuid.uuid4()) uuid1 = str(uuid.uuid4())
uuid2 = str(uuid.uuid4()) uuid2 = str(uuid.uuid4())
uuid3 = str(uuid.uuid4()) uuid3 = str(uuid.uuid4())
@ -180,11 +186,7 @@ class TestResourcePools:
await controller.add_project(project_id=uuid2, name="Project2", created_by=test_user.username) await controller.add_project(project_id=uuid2, name="Project2", created_by=test_user.username)
await controller.add_project(project_id=uuid3, name="Project3", created_by=test_user.username) await controller.add_project(project_id=uuid3, name="Project3", created_by=test_user.username)
# user has no access to projects (no ACE on /projects or resource pools) # Create resource pool and add uuid2 to it
response = await client.get(app.url_path_for("get_projects"))
assert response.status_code == status.HTTP_200_OK
assert len(response.json()) == 0
pools_repo = ResourcePoolsRepository(db_session) pools_repo = ResourcePoolsRepository(db_session)
new_resource_pool = ResourcePoolCreate(name="pool2") new_resource_pool = ResourcePoolCreate(name="pool2")
pool_in_db = await pools_repo.create_resource_pool(new_resource_pool) pool_in_db = await pools_repo.create_resource_pool(new_resource_pool)
@ -195,6 +197,8 @@ class TestResourcePools:
group_id = (await UsersRepository(db_session).get_user_group_by_name("Users")).user_group_id group_id = (await UsersRepository(db_session).get_user_group_by_name("Users")).user_group_id
role_id = (await RbacRepository(db_session).get_role_by_name("User")).role_id role_id = (await RbacRepository(db_session).get_role_by_name("User")).role_id
# Give user access to resource pool only
ace = ACECreate( ace = ACECreate(
path=f"/pools/{pool_in_db.resource_pool_id}", path=f"/pools/{pool_in_db.resource_pool_id}",
ace_type="group", ace_type="group",
@ -204,40 +208,47 @@ class TestResourcePools:
) )
await RbacRepository(db_session).create_ace(ace) await RbacRepository(db_session).create_ace(ace)
response = await client.get(app.url_path_for("get_project", project_id=uuid2)) # Create a new client with test user authentication
assert response.status_code == status.HTTP_200_OK access_token = auth_service.create_access_token(test_user.username)
assert response.json()["name"] == "Project2" async with AsyncClient(
base_url="http://test-api",
headers={"Content-Type": "application/json", "Authorization": f"Bearer {access_token}"},
transport=ASGIWebSocketTransport(app=app)
) as user_client:
# user should see only uuid2 (from resource pool)
response = await user_client.get(app.url_path_for("get_projects"))
assert response.status_code == status.HTTP_200_OK
projects = response.json()
assert len(projects) == 1
assert projects[0]["project_id"] == uuid2
# user should only see one project because it is in the resource pool he has access to response = await user_client.get(app.url_path_for("get_project", project_id=uuid2))
response = await client.get(app.url_path_for("get_projects")) assert response.status_code == status.HTTP_200_OK
assert response.status_code == status.HTTP_200_OK assert response.json()["name"] == "Project2"
projects = response.json()
assert len(projects) == 1
assert projects[0]["project_id"] == uuid2
ace = ACECreate( # Now give user access to /projects (in addition to resource pool)
path=f"/projects", ace = ACECreate(
ace_type="group", path="/projects",
propagate=True, ace_type="group",
group_id=str(group_id), propagate=True,
role_id=str(role_id) group_id=str(group_id),
) role_id=str(role_id)
await RbacRepository(db_session).create_ace(ace) )
await RbacRepository(db_session).create_ace(ace)
# now user should see all projects because he has access to /projects and the resource pool # user should see all 3 projects: 3 from /projects ACE (uuid2 also in pool but deduplicated)
response = await client.get(app.url_path_for("get_projects")) response = await user_client.get(app.url_path_for("get_projects"))
assert response.status_code == status.HTTP_200_OK assert response.status_code == status.HTTP_200_OK
projects = response.json() projects = response.json()
assert len(projects) == 3 assert len(projects) == 3
await RbacRepository(db_session).delete_all_ace_starting_with_path(f"/pools/{pool_in_db.resource_pool_id}") # Remove resource pool ACE
response = await client.get(app.url_path_for("get_project", project_id=uuid2)) await RbacRepository(db_session).delete_all_ace_starting_with_path(f"/pools/{pool_in_db.resource_pool_id}")
assert response.status_code == status.HTTP_403_FORBIDDEN
# now user should only see the projects that are not in a resource pool # user should still see all 3 projects via /projects ACE (created_by check)
response = await client.get(app.url_path_for("get_projects")) response = await user_client.get(app.url_path_for("get_projects"))
assert response.status_code == status.HTTP_200_OK assert response.status_code == status.HTTP_200_OK
assert len(response.json()) == 2 assert len(response.json()) == 3
# class TestProjectsWithRbac: # class TestProjectsWithRbac: