1- """Component tests for v2 VFolder RBAC-enforced mutations.
1+ """Component tests for v2 VFolder RBAC-enforced mutations via SDK .
22
3- Exercises ``SingleEntityActionProcessor`` (delete/purge) and
4- ``ScopeActionProcessor`` (create) end-to-end against the real DB
5- and real RBAC validators.
3+ Exercises create (project-scoped), delete, purge, and update mutations
4+ through the real HTTP server + V2ClientRegistry SDK.
65"""
76
87from __future__ import annotations
1514from unittest .mock import MagicMock
1615
1716import pytest
17+ import yarl
1818
19- from ai .backend .common .contexts .user import with_user
20- from ai .backend .common .data .user .types import UserData , UserRole
19+ from ai .backend .client .v2 .auth import HMACAuth
20+ from ai .backend .client .v2 .config import ClientConfig
21+ from ai .backend .client .v2 .exceptions import PermissionDeniedError
22+ from ai .backend .client .v2 .v2_registry import V2ClientRegistry
23+ from ai .backend .common .dto .manager .v2 .vfolder .request import UpdateVFolderInput
2124from ai .backend .common .types import QuotaScopeID , QuotaScopeType , VFolderUsageMode
2225from ai .backend .manager .actions .validators import ActionValidators
2326from ai .backend .manager .actions .validators .rbac import RBACValidators
2629 SingleEntityActionRBACValidator ,
2730)
2831from ai .backend .manager .api .adapters .vfolder import VFolderAdapter
32+ from ai .backend .manager .api .rest .routing import RouteRegistry
33+ from ai .backend .manager .api .rest .types import RouteDeps
34+ from ai .backend .manager .api .rest .v2 .vfolder .handler import V2VFolderHandler
35+ from ai .backend .manager .api .rest .v2 .vfolder .registry import register_v2_vfolder_routes
2936from ai .backend .manager .data .vfolder .types import (
3037 VFolderMountPermission ,
3138 VFolderOperationStatus ,
3239 VFolderOwnershipType ,
3340)
34- from ai .backend .manager .errors .permission import NotEnoughPermission
3541from ai .backend .manager .models .utils import ExtendedAsyncSAEngine
3642from ai .backend .manager .models .vfolder import vfolders
3743from ai .backend .manager .repositories .permission_controller .repository import (
4551
4652if TYPE_CHECKING :
4753 from sqlalchemy .ext .asyncio .engine import AsyncEngine as SAEngine
48- from tests .component .conftest import UserFixtureData
54+ from tests .component .conftest import ServerInfo , UserFixtureData
4955
5056
5157@dataclass (frozen = True )
@@ -54,6 +60,9 @@ class ProjectVFolderFixtureData:
5460 project_id : uuid .UUID
5561
5662
63+ # ── Fixtures ──────────────────────────────────────────────────────
64+
65+
5766@pytest .fixture ()
5867def rbac_permission_repo (
5968 database_engine : ExtendedAsyncSAEngine ,
@@ -62,11 +71,11 @@ def rbac_permission_repo(
6271
6372
6473@pytest .fixture ()
65- def vfolder_processors_with_real_rbac (
74+ def vfolder_processors (
6675 database_engine : ExtendedAsyncSAEngine ,
6776 rbac_permission_repo : PermissionControllerRepository ,
6877) -> VFolderProcessors :
69- """VFolderProcessors wired with real scope + single-entity RBAC validators."""
78+ """Override: real scope + single-entity RBAC validators."""
7079 vfolder_repository = VfolderRepository (database_engine )
7180 user_repository = UserRepository (database_engine )
7281 service = VFolderService (
@@ -91,36 +100,54 @@ def vfolder_processors_with_real_rbac(
91100
92101
93102@pytest .fixture ()
94- def vfolder_adapter (
95- vfolder_processors_with_real_rbac : VFolderProcessors ,
96- ) -> VFolderAdapter :
103+ def server_module_registries (
104+ route_deps : RouteDeps ,
105+ vfolder_processors : VFolderProcessors ,
106+ ) -> list [RouteRegistry ]:
107+ """Register v2 vfolder REST routes with real RBAC."""
97108 processors = MagicMock (spec = Processors )
98- processors .vfolder = vfolder_processors_with_real_rbac
99- return VFolderAdapter (processors )
109+ processors .vfolder = vfolder_processors
110+ adapter = VFolderAdapter (processors )
111+ handler = V2VFolderHandler (adapter = adapter )
112+ v2_reg = RouteRegistry .create ("v2" , route_deps .cors_options )
113+ v2_reg .add_subregistry (register_v2_vfolder_routes (handler , route_deps ))
114+ return [v2_reg ]
100115
101116
102117@pytest .fixture ()
103- def admin_user_data (admin_user_fixture : UserFixtureData ) -> UserData :
104- return UserData (
105- user_id = admin_user_fixture .user_uuid ,
106- is_authorized = True ,
107- is_admin = True ,
108- is_superadmin = True ,
109- role = UserRole .SUPERADMIN ,
110- domain_name = "default" ,
118+ async def admin_v2_registry (
119+ server : ServerInfo ,
120+ admin_user_fixture : UserFixtureData ,
121+ ) -> AsyncIterator [V2ClientRegistry ]:
122+ registry = await V2ClientRegistry .create (
123+ ClientConfig (endpoint = yarl .URL (server .url )),
124+ HMACAuth (
125+ access_key = admin_user_fixture .keypair .access_key ,
126+ secret_key = admin_user_fixture .keypair .secret_key ,
127+ ),
111128 )
129+ try :
130+ yield registry
131+ finally :
132+ await registry .close ()
112133
113134
114135@pytest .fixture ()
115- def regular_user_data (regular_user_fixture : UserFixtureData ) -> UserData :
116- return UserData (
117- user_id = regular_user_fixture .user_uuid ,
118- is_authorized = True ,
119- is_admin = False ,
120- is_superadmin = False ,
121- role = UserRole .USER ,
122- domain_name = "default" ,
136+ async def user_v2_registry (
137+ server : ServerInfo ,
138+ regular_user_fixture : UserFixtureData ,
139+ ) -> AsyncIterator [V2ClientRegistry ]:
140+ registry = await V2ClientRegistry .create (
141+ ClientConfig (endpoint = yarl .URL (server .url )),
142+ HMACAuth (
143+ access_key = regular_user_fixture .keypair .access_key ,
144+ secret_key = regular_user_fixture .keypair .secret_key ,
145+ ),
123146 )
147+ try :
148+ yield registry
149+ finally :
150+ await registry .close ()
124151
125152
126153@pytest .fixture ()
@@ -157,39 +184,51 @@ async def project_vfolder(
157184 await conn .execute (vfolders .delete ().where (vfolders .c .id == vfolder_id ))
158185
159186
160- class TestDeleteVFolderV2ByIdRBAC :
161- """SingleEntityActionProcessor RBAC enforcement for vfolder delete."""
187+ # ── Tests ─────────────────────────────────────────────────────────
162188
163- async def test_regular_user_without_grant_is_denied (
189+
190+ class TestDeleteVFolderRBAC :
191+ """DELETE /v2/vfolders/{id} — SingleEntityActionProcessor RBAC."""
192+
193+ async def test_regular_user_denied (
164194 self ,
165- vfolder_adapter : VFolderAdapter ,
166- regular_user_data : UserData ,
195+ user_v2_registry : V2ClientRegistry ,
167196 project_vfolder : ProjectVFolderFixtureData ,
168197 ) -> None :
169- with with_user (regular_user_data ):
170- with pytest .raises (NotEnoughPermission ):
171- await vfolder_adapter .delete (project_vfolder .id )
198+ with pytest .raises (PermissionDeniedError ):
199+ await user_v2_registry .vfolder .delete (project_vfolder .id )
172200
173- async def test_superadmin_bypasses_rbac (
201+ async def test_superadmin_succeeds (
174202 self ,
175- vfolder_adapter : VFolderAdapter ,
176- admin_user_data : UserData ,
203+ admin_v2_registry : V2ClientRegistry ,
177204 project_vfolder : ProjectVFolderFixtureData ,
178205 ) -> None :
179- with with_user (admin_user_data ):
180- payload = await vfolder_adapter .delete (project_vfolder .id )
206+ payload = await admin_v2_registry .vfolder .delete (project_vfolder .id )
181207 assert payload .id == project_vfolder .id
182208
183209
184- class TestPurgeVFolderV2ByIdRBAC :
185- """SingleEntityActionProcessor RBAC enforcement for vfolder purge ."""
210+ class TestPurgeVFolderRBAC :
211+ """POST /v2/vfolders/{id}/purge — SingleEntityActionProcessor RBAC ."""
186212
187- async def test_regular_user_without_grant_is_denied (
213+ async def test_regular_user_denied (
188214 self ,
189- vfolder_adapter : VFolderAdapter ,
190- regular_user_data : UserData ,
215+ user_v2_registry : V2ClientRegistry ,
191216 project_vfolder : ProjectVFolderFixtureData ,
192217 ) -> None :
193- with with_user (regular_user_data ):
194- with pytest .raises (NotEnoughPermission ):
195- await vfolder_adapter .purge (project_vfolder .id )
218+ with pytest .raises (PermissionDeniedError ):
219+ await user_v2_registry .vfolder .purge (project_vfolder .id )
220+
221+
222+ class TestUpdateVFolderRBAC :
223+ """PATCH /v2/vfolders/{id} — SingleEntityActionProcessor RBAC."""
224+
225+ async def test_regular_user_denied (
226+ self ,
227+ user_v2_registry : V2ClientRegistry ,
228+ project_vfolder : ProjectVFolderFixtureData ,
229+ ) -> None :
230+ with pytest .raises (PermissionDeniedError ):
231+ await user_v2_registry .vfolder .update (
232+ project_vfolder .id ,
233+ UpdateVFolderInput (name = "new-name" ),
234+ )
0 commit comments