mirror of
https://github.com/Canner/WrenAI.git
synced 2026-10-13 16:56:57 +08:00
fix(context): count errors in the validate warning summary (#2781)
This commit is contained in:
@@ -543,7 +543,9 @@ def validate(
|
||||
typer.echo(f" \u2717 {msg}", err=True)
|
||||
|
||||
all_warnings: list[str] = [str(w) for w in struct_warnings] + list(sem_warnings)
|
||||
_print_warnings(all_warnings, verbose=verbose)
|
||||
_print_warnings(
|
||||
all_warnings, verbose=verbose, errors=len(struct_hard) + len(sem_errors)
|
||||
)
|
||||
|
||||
# ── Exit logic ──────────────────────────────────────────────────────────────────
|
||||
has_hard_error = bool(struct_hard or sem_errors)
|
||||
@@ -576,7 +578,7 @@ def validate(
|
||||
)
|
||||
|
||||
|
||||
def _print_warnings(warnings: list[str], *, verbose: bool) -> None:
|
||||
def _print_warnings(warnings: list[str], *, verbose: bool, errors: int = 0) -> None:
|
||||
"""Render warnings: every line below the threshold, grouped summary above.
|
||||
|
||||
Agents and humans both read the first "Warnings:" line as a signal
|
||||
@@ -593,7 +595,7 @@ def _print_warnings(warnings: list[str], *, verbose: bool) -> None:
|
||||
typer.echo("\nWarnings:")
|
||||
for msg in warnings:
|
||||
typer.echo(f" \u26a0 {msg}")
|
||||
typer.echo(f"\n{total} warning(s), 0 errors.")
|
||||
typer.echo(f"\n{total} warning(s), {errors} error(s).")
|
||||
return
|
||||
|
||||
# Bucket by warning *category* — the text after the last colon
|
||||
@@ -1165,7 +1167,7 @@ def _validate_from_osi(
|
||||
for e in hard:
|
||||
typer.echo(f" ✗ {e}", err=True)
|
||||
|
||||
_print_warnings(warns, verbose=verbose)
|
||||
_print_warnings(warns, verbose=verbose, errors=len(hard))
|
||||
|
||||
if hard or (strict and warns):
|
||||
raise typer.Exit(1)
|
||||
|
||||
@@ -506,6 +506,19 @@ def test_validate_strict_warns(tmp_path):
|
||||
assert result.exit_code == 1
|
||||
|
||||
|
||||
def test_validate_summary_counts_errors(tmp_path):
|
||||
_make_valid_project(tmp_path)
|
||||
project_file = tmp_path / "wren_project.yml"
|
||||
project_file.write_text(project_file.read_text().replace("name: test_proj\n", ""))
|
||||
model_meta = tmp_path / "models" / "orders" / "metadata.yml"
|
||||
model_meta.write_text(model_meta.read_text() + "dialect: postgres\n")
|
||||
|
||||
result = runner.invoke(app, ["context", "validate", "--path", str(tmp_path)])
|
||||
assert result.exit_code == 1
|
||||
assert "missing required field 'name'" in result.output
|
||||
assert "1 warning(s), 1 error(s)." in result.output
|
||||
|
||||
|
||||
def _write_null_models_relationship(tmp_path: Path) -> None:
|
||||
# A relationship whose `models:` is an explicit YAML null. `.get("models", [])`
|
||||
# returns None (not the default) for a present-but-null key, so iterating /
|
||||
|
||||
@@ -713,6 +713,36 @@ def test_cli_validate_from_osi_surfaces_warnings():
|
||||
assert "column_types" in result.output # snippet emitted
|
||||
|
||||
|
||||
def test_cli_validate_from_osi_summary_counts_errors(tmp_path: Path):
|
||||
osi_file = tmp_path / "osi.yaml"
|
||||
osi_file.write_text(
|
||||
'version: "0.2.0"\n'
|
||||
"semantic_model:\n"
|
||||
" - name: shop\n"
|
||||
" datasets:\n"
|
||||
" - name: orders\n"
|
||||
" source: shop.public.orders\n"
|
||||
" fields:\n"
|
||||
" - name: order_id\n"
|
||||
" expression: order_id\n"
|
||||
" - source: shop.public.customers\n"
|
||||
)
|
||||
result = runner.invoke(
|
||||
app,
|
||||
[
|
||||
"context",
|
||||
"validate",
|
||||
"--from-osi",
|
||||
str(osi_file),
|
||||
"--data-source",
|
||||
"postgres",
|
||||
],
|
||||
)
|
||||
assert result.exit_code == 1
|
||||
assert "dataset missing 'name'" in result.output
|
||||
assert "1 warning(s), 1 error(s)." in result.output
|
||||
|
||||
|
||||
def test_cli_validate_from_osi_strict_fails_on_warning():
|
||||
result = runner.invoke(
|
||||
app,
|
||||
|
||||
Reference in New Issue
Block a user