Skip to content

Commit 726ddc8

Browse files
committed
refactor(BA-2841): Extract spec instantiation into fixtures in ImageCreatorAdapter tests
- Add 4 scenario-specific spec fixtures - Simplify test functions to use fixtures instead of inline spec creation
1 parent 1ebb555 commit 726ddc8

File tree

3 files changed

+42
-40
lines changed

3 files changed

+42
-40
lines changed

src/ai/backend/manager/container_registry/base.py

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,8 @@
2222
import yarl
2323

2424
from ai.backend.common.bgtask.reporter import ProgressReporter
25-
from ai.backend.common.data.permission.types import GLOBAL_SCOPE_ID, EntityType, ScopeType
2625
from ai.backend.common.docker import (
2726
ImageRef,
28-
LabelName,
2927
arch_name_aliases,
3028
validate_image_labels,
3129
)
@@ -50,9 +48,9 @@
5048
from ai.backend.manager.models.image import ImageIdentifier, ImageRow
5149
from ai.backend.manager.models.utils import ExtendedAsyncSAEngine
5250
from ai.backend.manager.repositories.base.rbac.entity_creator import (
53-
RBACEntityCreator,
5451
execute_rbac_entity_creator,
5552
)
53+
from ai.backend.manager.repositories.image.adapter import ImageCreatorAdapter
5654
from ai.backend.manager.repositories.image.creators import ImageRowCreatorSpec
5755

5856
log = BraceStyleAdapter(logging.getLogger(__spec__.name))
@@ -110,6 +108,7 @@ def __init__(
110108
}
111109
self.credentials = {}
112110
self.ssl_verify = ssl_verify
111+
self._creator_adapter = ImageCreatorAdapter()
113112

114113
@actxmgr
115114
async def prepare_client_session(self) -> AsyncIterator[tuple[yarl.URL, aiohttp.ClientSession]]:
@@ -207,16 +206,7 @@ async def commit_rescan_result(self) -> list[ImageData]:
207206
await reporter.update(1, message=progress_msg)
208207
continue
209208

210-
labels = update["labels"]
211-
owner_labels = labels.get(LabelName.CUSTOMIZED_OWNER)
212-
if owner_labels is not None:
213-
scope_type = ScopeType.USER
214-
_, _, scope_id = owner_labels.partition(":")
215-
else:
216-
scope_type = ScopeType.GLOBAL
217-
scope_id = GLOBAL_SCOPE_ID
218-
219-
rbac_creator = RBACEntityCreator(
209+
rbac_creator = self._creator_adapter.build(
220210
spec=ImageRowCreatorSpec(
221211
name=parsed_img.canonical,
222212
project=self.registry_info.project,
@@ -232,10 +222,7 @@ async def commit_rescan_result(self) -> list[ImageData]:
232222
accelerators=update.get("accels"),
233223
labels=update["labels"],
234224
status=ImageStatus.ALIVE,
235-
),
236-
scope_type=scope_type,
237-
scope_id=scope_id,
238-
entity_type=EntityType.IMAGE,
225+
)
239226
)
240227
result = await execute_rbac_entity_creator(session, rbac_creator)
241228
scanned_images.append(result.row.to_dataclass())

src/ai/backend/manager/repositories/image/adapter.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
from ai.backend.common.data.permission.types import GLOBAL_SCOPE_ID, EntityType, ScopeType
66
from ai.backend.common.docker import LabelName
7+
from ai.backend.manager.models.image.row import ImageRow
78
from ai.backend.manager.repositories.base.rbac.adapter import CreatorAdapter
89
from ai.backend.manager.repositories.base.rbac.entity_creator import RBACEntityCreator
910
from ai.backend.manager.repositories.image.creators import ImageRowCreatorSpec
@@ -18,7 +19,7 @@ class ImageCreatorAdapter(CreatorAdapter[ImageRowCreatorSpec]):
1819
"""
1920

2021
@override
21-
def build(self, spec: ImageRowCreatorSpec) -> RBACEntityCreator:
22+
def build(self, spec: ImageRowCreatorSpec) -> RBACEntityCreator[ImageRow]:
2223
labels: dict[str, Any] = spec.labels or {}
2324
owner_label = labels.get(LabelName.CUSTOMIZED_OWNER)
2425

tests/unit/manager/repositories/image/test_adapter.py

Lines changed: 36 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -34,19 +34,42 @@ def base_spec_kwargs(self) -> dict[str, Any]:
3434
def user_id(self) -> str:
3535
return str(uuid4())
3636

37+
@pytest.fixture
38+
def spec_with_owner_label(
39+
self, base_spec_kwargs: dict[str, Any], user_id: str
40+
) -> ImageRowCreatorSpec:
41+
"""Spec with owner label for USER scope."""
42+
return ImageRowCreatorSpec(
43+
**base_spec_kwargs,
44+
labels={LabelName.CUSTOMIZED_OWNER: f"user:{user_id}"},
45+
)
46+
47+
@pytest.fixture
48+
def spec_without_owner_label(self, base_spec_kwargs: dict[str, Any]) -> ImageRowCreatorSpec:
49+
"""Spec with unrelated label for GLOBAL scope."""
50+
return ImageRowCreatorSpec(
51+
**base_spec_kwargs,
52+
labels={"other_label": "value"},
53+
)
54+
55+
@pytest.fixture
56+
def spec_with_empty_labels(self, base_spec_kwargs: dict[str, Any]) -> ImageRowCreatorSpec:
57+
"""Spec with empty labels dict for GLOBAL scope."""
58+
return ImageRowCreatorSpec(**base_spec_kwargs, labels={})
59+
60+
@pytest.fixture
61+
def spec_with_none_labels(self, base_spec_kwargs: dict[str, Any]) -> ImageRowCreatorSpec:
62+
"""Spec with None labels for GLOBAL scope."""
63+
return ImageRowCreatorSpec(**base_spec_kwargs, labels=None)
64+
3765
def test_build_with_owner_label_returns_user_scope(
3866
self,
3967
adapter: ImageCreatorAdapter,
40-
base_spec_kwargs: dict[str, Any],
68+
spec_with_owner_label: ImageRowCreatorSpec,
4169
user_id: str,
4270
) -> None:
4371
"""Should return USER scope when owner label exists."""
44-
spec = ImageRowCreatorSpec(
45-
**base_spec_kwargs,
46-
labels={LabelName.CUSTOMIZED_OWNER: f"user:{user_id}"},
47-
)
48-
49-
creator = adapter.build(spec)
72+
creator = adapter.build(spec_with_owner_label)
5073

5174
assert creator.scope_type == ScopeType.USER
5275
assert creator.scope_id == user_id
@@ -55,15 +78,10 @@ def test_build_with_owner_label_returns_user_scope(
5578
def test_build_without_owner_label_returns_global_scope(
5679
self,
5780
adapter: ImageCreatorAdapter,
58-
base_spec_kwargs: dict[str, Any],
81+
spec_without_owner_label: ImageRowCreatorSpec,
5982
) -> None:
6083
"""Should return GLOBAL scope when owner label is missing."""
61-
spec = ImageRowCreatorSpec(
62-
**base_spec_kwargs,
63-
labels={"other_label": "value"},
64-
)
65-
66-
creator = adapter.build(spec)
84+
creator = adapter.build(spec_without_owner_label)
6785

6886
assert creator.scope_type == ScopeType.GLOBAL
6987
assert creator.scope_id == GLOBAL_SCOPE_ID
@@ -72,25 +90,21 @@ def test_build_without_owner_label_returns_global_scope(
7290
def test_build_with_empty_labels_returns_global_scope(
7391
self,
7492
adapter: ImageCreatorAdapter,
75-
base_spec_kwargs: dict[str, Any],
93+
spec_with_empty_labels: ImageRowCreatorSpec,
7694
) -> None:
7795
"""Should return GLOBAL scope when labels dict is empty."""
78-
spec = ImageRowCreatorSpec(**base_spec_kwargs, labels={})
79-
80-
creator = adapter.build(spec)
96+
creator = adapter.build(spec_with_empty_labels)
8197

8298
assert creator.scope_type == ScopeType.GLOBAL
8399
assert creator.scope_id == GLOBAL_SCOPE_ID
84100

85101
def test_build_with_none_labels_returns_global_scope(
86102
self,
87103
adapter: ImageCreatorAdapter,
88-
base_spec_kwargs: dict[str, Any],
104+
spec_with_none_labels: ImageRowCreatorSpec,
89105
) -> None:
90106
"""Should return GLOBAL scope when labels is None."""
91-
spec = ImageRowCreatorSpec(**base_spec_kwargs, labels=None)
92-
93-
creator = adapter.build(spec)
107+
creator = adapter.build(spec_with_none_labels)
94108

95109
assert creator.scope_type == ScopeType.GLOBAL
96110
assert creator.scope_id == GLOBAL_SCOPE_ID

0 commit comments

Comments
 (0)