Skip to content

Commit 90cf35f

Browse files
feat: [SNOW-2326970] implement dcm test command
1 parent e0836d1 commit 90cf35f

File tree

7 files changed

+509
-11
lines changed

7 files changed

+509
-11
lines changed

src/snowflake/cli/_plugins/dcm/commands.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@
1111
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
14+
import json
1415
from typing import List, Optional
1516

1617
import typer
1718
from snowflake.cli._plugins.dcm.manager import DCMProjectManager
19+
from snowflake.cli._plugins.dcm.utils import format_test_failures
1820
from snowflake.cli._plugins.object.command_aliases import add_object_command_aliases
1921
from snowflake.cli._plugins.object.commands import scope_option
2022
from snowflake.cli._plugins.object.manager import ObjectManager
@@ -235,6 +237,47 @@ def drop_deployment(
235237
)
236238

237239

240+
@app.command(requires_connection=True)
241+
def test(
242+
identifier: FQN = dcm_identifier,
243+
**options,
244+
):
245+
"""
246+
Test all expectations set for tables, views and dynamic tables defined
247+
in DCM project.
248+
"""
249+
with cli_console.spinner() as spinner:
250+
spinner.add_task(description=f"Testing dcm project {identifier}", total=None)
251+
result = DCMProjectManager().test(project_identifier=identifier)
252+
253+
row = result.fetchone()
254+
if not row:
255+
return MessageResult("No data.")
256+
257+
result_data = row[0]
258+
result_json = (
259+
json.loads(result_data) if isinstance(result_data, str) else result_data
260+
)
261+
262+
expectations = result_json.get("expectations", [])
263+
264+
if not expectations:
265+
return MessageResult("No expectations defined in the project.")
266+
267+
if result_json.get("status") == "EXPECTATION_VIOLATED":
268+
failed_expectations = [
269+
exp for exp in expectations if exp.get("expectation_violated", False)
270+
]
271+
total_tests = len(expectations)
272+
failed_count = len(failed_expectations)
273+
error_message = format_test_failures(
274+
failed_expectations, total_tests, failed_count
275+
)
276+
raise CliError(error_message)
277+
278+
return MessageResult(f"All {len(expectations)} expectation(s) passed successfully.")
279+
280+
238281
def _get_effective_stage(identifier: FQN, from_location: Optional[str]):
239282
manager = DCMProjectManager()
240283
if not from_location:

src/snowflake/cli/_plugins/dcm/manager.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,10 @@ def drop_deployment(
136136
query += f' "{deployment_name}"'
137137
return self.execute_query(query=query)
138138

139+
def test(self, project_identifier: FQN):
140+
query = f"EXECUTE DCM PROJECT {project_identifier.sql_identifier} TEST ALL"
141+
return self.execute_query(query=query)
142+
139143
@staticmethod
140144
def sync_local_files(
141145
project_identifier: FQN, source_directory: str | None = None
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Copyright (c) 2024 Snowflake Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
16+
def format_test_failures(
17+
failed_expectations: list, total_tests: int, failed_count: int
18+
) -> str:
19+
"""Format test failures into a nice error message."""
20+
lines = [
21+
"Failed expectations:",
22+
]
23+
24+
for failed in failed_expectations:
25+
table_name = failed.get("table_name", "Unknown")
26+
expectation_name = failed.get("expectation_name", "Unknown")
27+
metric_name = failed.get("metric_name", "Unknown")
28+
expectation_expr = failed.get("expectation_expression", "N/A")
29+
value = failed.get("value", "N/A")
30+
31+
lines.append(f" Table: {table_name}")
32+
lines.append(f" Expectation: {expectation_name}")
33+
lines.append(f" Metric: {metric_name}")
34+
lines.append(f" Expression: {expectation_expr}")
35+
lines.append(f" Actual value: {value}")
36+
lines.append("")
37+
38+
passed_tests = total_tests - failed_count
39+
lines.append(
40+
f"Tests completed: {passed_tests} passed, {failed_count} failed out of {total_tests} total."
41+
)
42+
43+
return "\n".join(lines)

0 commit comments

Comments
 (0)