Skip to content

Commit 26a012c

Browse files
ronpaldoctrino
andauthored
[CDF-27420] Validate SearchConfig referenced view in build v2 (#2775)
# Description `SearchConfig.yaml` could reference a view that does not exist without Toolkit reporting it. `SearchConfigCRUD` did not declare a view dependency, so build v2 dependency validation did not include SearchConfig view references. This change: - Declares the configured view as a `ViewCRUD` dependency via `SearchConfigCRUD.get_dependencies`. - Implements `ViewId`/`ViewNoVersionId` equality and hash behavior so no-version view references match versioned views in dependency checks. - Adds focused unit tests for SearchConfig dependency validation and identifier equality behavior. ## Bump - [x] Patch - [ ] Skip ## Changelog ### Fixed - SearchConfig (build v2): emit `MISSING-DEPENDENCY` when the referenced data modeling view is missing from the build output and from CDF. --------- Co-authored-by: anders-albert <anders0albert@gmail.com>
1 parent 983eaf1 commit 26a012c

File tree

4 files changed

+90
-2
lines changed

4 files changed

+90
-2
lines changed

cognite_toolkit/_cdf_tk/client/identifiers/_data_modeling.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,10 +85,29 @@ def _as_filename(self, include_type: bool = False) -> str:
8585
return f"space-{self.space}.externalId-{self.external_id}"
8686
return f"{self.space}.{self.external_id}"
8787

88+
def __eq__(self, other: Any) -> bool:
89+
if isinstance(other, ViewNoVersionId):
90+
return self.space == other.space and self.external_id == other.external_id
91+
return NotImplemented
92+
93+
def __hash__(self) -> int:
94+
return hash((self.space, self.external_id))
95+
8896

8997
class ViewId(ViewNoVersionId):
9098
version: str
9199

100+
def __eq__(self, other: Any) -> bool:
101+
if isinstance(other, ViewId):
102+
return self.space == other.space and self.external_id == other.external_id and self.version == other.version
103+
if isinstance(other, ViewNoVersionId):
104+
return self.space == other.space and self.external_id == other.external_id
105+
return NotImplemented
106+
107+
def __hash__(self) -> int:
108+
# Keep hash aligned with equality against ViewNoVersionId.
109+
return hash((self.space, self.external_id))
110+
92111
def __str__(self) -> str:
93112
return f"{self.space}:{self.external_id}(version={self.version})"
94113

cognite_toolkit/_cdf_tk/cruds/_resource_cruds/configuration.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from rich.console import Console
66

77
from cognite_toolkit._cdf_tk.client import ToolkitClient
8+
from cognite_toolkit._cdf_tk.client._resource_base import Identifier
89
from cognite_toolkit._cdf_tk.client.resource_classes.data_modeling import ViewNoVersionId
910
from cognite_toolkit._cdf_tk.client.resource_classes.group import (
1011
AclType,
@@ -66,6 +67,10 @@ def dump_id(cls, id: ViewNoVersionId) -> dict[str, Any]:
6667
def as_str(cls, id: ViewNoVersionId) -> str:
6768
return sanitize_filename(f"{id.external_id}_{id.space}")
6869

70+
@classmethod
71+
def get_dependencies(cls, resource: SearchConfigYAML) -> Iterable[tuple[type[ResourceCRUD], Identifier]]:
72+
yield ViewCRUD, resource.as_id()
73+
6974
def dump_resource(self, resource: SearchConfigResponse, local: dict[str, Any] | None = None) -> dict[str, Any]:
7075
dumped = resource.as_request_resource().dump()
7176
local = local or {}

tests/test_unit/test_cdf_tk/test_client/test_identifiers.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from cognite_toolkit._cdf_tk.client._resource_base import Identifier
44

55
# Import one of the concrete Identifier subclasses to ensure they are registered in the test
6-
from cognite_toolkit._cdf_tk.client.identifiers import EdgeTypeId, NodeId
6+
from cognite_toolkit._cdf_tk.client.identifiers import EdgeTypeId, NodeId, ViewId, ViewNoVersionId
77

88

99
class TestIdentifiers:
@@ -25,3 +25,14 @@ def test_validate_str(self) -> None:
2525
edge_type = EdgeTypeId(type=NodeId(space="test_space", external_id="test_external_id"), direction="outwards")
2626

2727
assert edge_type == EdgeTypeId.model_validate(str(edge_type))
28+
29+
30+
class TestViewIdEquality:
31+
def test_view_no_version_equals_versioned_view(self) -> None:
32+
v1 = ViewId(space="test_space", external_id="test_external_id", version="v1")
33+
v2 = ViewId(space="test_space", external_id="test_external_id", version="v2")
34+
no_version = ViewNoVersionId(space=v1.space, external_id=v1.external_id)
35+
36+
assert v1 != v2
37+
assert no_version == v1
38+
assert v2 == no_version

tests/test_unit/test_cdf_tk/test_commands/test_buildv2/test_command.py

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,19 @@
88

99
from cognite_toolkit._cdf_tk.client._toolkit_client import ToolkitClient
1010
from cognite_toolkit._cdf_tk.client.config import ToolkitClientConfig
11+
from cognite_toolkit._cdf_tk.client.identifiers import ViewId, ViewNoVersionId
1112
from cognite_toolkit._cdf_tk.commands import BuildV2Command
1213
from cognite_toolkit._cdf_tk.commands.build_v2.data_classes import BuildParameters, RelativeDirPath
14+
from cognite_toolkit._cdf_tk.commands.build_v2.data_classes._build import BuiltModule, BuiltResource
1315
from cognite_toolkit._cdf_tk.commands.build_v2.data_classes._module import (
1416
FailedReadYAMLFile,
17+
ModuleId,
18+
ResourceType,
1519
SuccessfulReadYAMLFile,
1620
)
21+
from cognite_toolkit._cdf_tk.commands.build_v2.data_classes._types import AbsoluteDirPath, AbsoluteFilePath
1722
from cognite_toolkit._cdf_tk.constants import MODULES
18-
from cognite_toolkit._cdf_tk.cruds import SpaceCRUD
23+
from cognite_toolkit._cdf_tk.cruds import SearchConfigCRUD, SpaceCRUD
1924
from cognite_toolkit._cdf_tk.cruds._base_cruds import ResourceContainerCRUD, ResourceCRUD
2025
from cognite_toolkit._cdf_tk.cruds._resource_cruds.datamodel import DataModelCRUD, ViewCRUD
2126
from cognite_toolkit._cdf_tk.cruds._resource_cruds.workflow import WorkflowCRUD
@@ -192,6 +197,54 @@ def test_end_to_end_invalid_space_emits_syntax_warning(self, tmp_path: Path, tlk
192197
}
193198

194199

200+
class TestDependencyValidationSearchConfig:
201+
@staticmethod
202+
def _minimal_module(tmp_path: Path) -> tuple[BuiltModule, Path, Path]:
203+
mod_path = tmp_path / "modules" / "my"
204+
mod_path.mkdir(parents=True)
205+
source_file = mod_path / "1-x.SearchConfig.yaml"
206+
source_file.touch()
207+
build_file = mod_path / "1-x-out.SearchConfig.yaml"
208+
build_file.touch()
209+
module = BuiltModule(
210+
module_id=ModuleId(id=RelativeDirPath(Path("modules/my")), path=AbsoluteDirPath(mod_path.resolve())),
211+
resources=[],
212+
)
213+
return module, source_file, build_file
214+
215+
def test_search_config_dependency_satisfied_by_local_view(self, tmp_path: Path) -> None:
216+
module, source_file, build_file = self._minimal_module(tmp_path)
217+
view_ref = ViewNoVersionId(space="my_space", external_id="View1")
218+
module.resources.append(
219+
BuiltResource(
220+
identifier=ViewId(space="my_space", external_id="View1", version="v1"),
221+
source_hash="h-view",
222+
type=ResourceType(resource_folder=ViewCRUD.folder_name, kind=ViewCRUD.kind),
223+
source_path=AbsoluteFilePath(source_file.resolve()),
224+
build_path=AbsoluteFilePath(build_file.resolve()),
225+
crud_cls=ViewCRUD,
226+
dependencies=set(),
227+
)
228+
)
229+
module.resources.append(
230+
BuiltResource(
231+
identifier=view_ref,
232+
source_hash="h",
233+
type=ResourceType(
234+
resource_folder=SearchConfigCRUD.folder_name,
235+
kind=SearchConfigCRUD.kind,
236+
),
237+
source_path=AbsoluteFilePath(source_file.resolve()),
238+
build_path=AbsoluteFilePath(build_file.resolve()),
239+
crud_cls=SearchConfigCRUD,
240+
dependencies={(ViewCRUD, view_ref)},
241+
)
242+
)
243+
244+
insights = BuildV2Command()._dependency_validation([module], None)
245+
assert not any(getattr(i, "code", None) == "MISSING-DEPENDENCY" for i in insights)
246+
247+
195248
class TestValidateBuildParameters:
196249
@pytest.mark.parametrize(
197250
"paths, parameters, user_args, expected_error",

0 commit comments

Comments
 (0)