diff --git a/CHANGELOG.md b/CHANGELOG.md index f5f7bb2..967a478 100644 Binary files a/CHANGELOG.md and b/CHANGELOG.md differ diff --git a/src/schemaforge/check.py b/src/schemaforge/check.py index f69a2b5..0f82245 100644 --- a/src/schemaforge/check.py +++ b/src/schemaforge/check.py @@ -73,7 +73,7 @@ def check_directory( failures.append(f" FAIL {name}: {e}") if not converted: - return "No files could be converted to {}\n{}".format( + return "FAIL: No files could be converted to {}\n{}".format( canonical, "\n".join(failures) ) @@ -109,10 +109,18 @@ def check_directory( lines.append("") for m in mismatches: lines.append(m) - lines.append("FAIL: Schema files are not equivalent") + + # Deterministic exit-status lines. The CLI keys its process exit code off + # these. A conversion failure must fail the check even when there is no + # pairwise mismatch -- previously the "Mismatches: 0" report with no PASS + # line let CI report green while a schema silently failed to parse. + if mismatches or failures: + if mismatches: + lines.append("FAIL: Schema files are not equivalent") + if failures: + lines.append(f"FAIL: {len(failures)} file(s) failed to convert") else: lines.append(" Mismatches: 0") - if not failures: - lines.append("PASS: All schema files are equivalent") + lines.append("PASS: All schema files are equivalent") return "\n".join(lines) diff --git a/src/schemaforge/cli.py b/src/schemaforge/cli.py index ce984d4..a011f2d 100644 --- a/src/schemaforge/cli.py +++ b/src/schemaforge/cli.py @@ -177,7 +177,7 @@ def check(directory: str, canonical: str, type_map_path: str | None) -> None: try: result = check_directory(directory, canonical=canonical, type_map_path=type_map_path) click.echo(result) - if "FAIL" in result and "PASS" not in result: + if "FAIL:" in result: sys.exit(1) except (NotADirectoryError, ValueError, FileNotFoundError) as e: click.echo(f"Error: {e}", err=True) diff --git a/tests/test_check.py b/tests/test_check.py index 1030e1b..f8382be 100644 --- a/tests/test_check.py +++ b/tests/test_check.py @@ -138,3 +138,20 @@ def test_check_directory_canonical_format(): result = check_directory(tmpdir, canonical="prisma") assert "compared via prisma" in result + + +def test_check_directory_conversion_failure_reports_fail(): + """A file that fails to parse must produce a FAIL line (not a silent PASS). + + Regression guard: previously a conversion failure with no pairwise mismatch + yielded "Mismatches: 0" and no PASS/FAIL line, so the CLI exited 0 (green) + while a schema silently failed to parse. + """ + with tempfile.TemporaryDirectory() as tmpdir: + Path(tmpdir, "schema.sql").write_text(SAMPLE_SQL) + Path(tmpdir, "broken.json").write_text("{ this is not valid json ,,, }") + + result = check_directory(tmpdir) + assert "Conversion failures: 1" in result + assert "FAIL: 1 file(s) failed to convert" in result + assert "PASS:" not in result diff --git a/tests/test_cli.py b/tests/test_cli.py index ddaecaa..b5ed620 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -509,10 +509,35 @@ def test_check_invalid_directory(self): finally: Path(f_path).unlink(missing_ok=True) + def test_check_cli_exits_nonzero_on_conversion_failure(self): + """Regression: a schema that fails to parse must make `check` exit non-zero. + + Previously the CLI only failed on mismatches, so a parse failure with no + pairwise mismatch reported "Mismatches: 0" and exited 0 (silent green). + """ + runner = CliRunner() + with tempfile.TemporaryDirectory() as tmpdir: + Path(tmpdir, "schema.sql").write_text(SAMPLE_SQL) + Path(tmpdir, "broken.json").write_text("{ this is not valid json ,,, }") + + result = runner.invoke(main, ["check", "--dir", tmpdir]) + assert result.exit_code != 0 + assert "FAIL:" in result.output + + def test_check_cli_exits_zero_when_all_equivalent(self): + """Two identical schema files are trivially equivalent -> exit 0 with PASS.""" + runner = CliRunner() + with tempfile.TemporaryDirectory() as tmpdir: + Path(tmpdir, "a.sql").write_text(SAMPLE_SQL) + Path(tmpdir, "b.sql").write_text(SAMPLE_SQL) + + result = runner.invoke(main, ["check", "--dir", tmpdir]) + assert result.exit_code == 0 + assert "PASS: All schema files are equivalent" in result.output + # ═══════════════════════════════════════════════════════════════ # mcp command -# ═══════════════════════════════════════════════════════════════ class TestMcpCommand: