Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions ifixai/cli/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,12 @@ async def _probe_then_close(
await _aclose_provider(provider)


def _validate_min_score(ctx: click.Context, param: click.Parameter, value: float) -> float:
if not 0 <= value <= 1:
raise click.BadParameter("must be a finite number between 0 and 1", ctx=ctx, param=param)
return value


@click.command()
@click.option(
"--provider",
Expand Down Expand Up @@ -495,6 +501,7 @@ async def _probe_then_close(
@click.option(
"--min-score",
type=float,
callback=_validate_min_score,
default=0.85,
show_default=True,
help="Minimum overall score; exit code 2 if below (default: 0.85 per ifixai spec).",
Expand Down
30 changes: 30 additions & 0 deletions ifixai/tests/test_cli_keyless_providers.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,3 +119,33 @@ def test_authenticated_provider_still_rejects_missing_key(
assert result.exit_code == 1
assert "No API key found" in result.output
assert not (tmp_path / "ifixai-results").exists()


@pytest.mark.parametrize("threshold", ["nan", "inf", "-inf", "-0.1", "1.1"])
def test_invalid_minimum_score_fails_before_run(tmp_path, monkeypatch, threshold):
monkeypatch.chdir(tmp_path)
result = CliRunner().invoke(ifixai_cli, [
"run", "--provider", "mock", "--fixture", "customer_support",
"--test", "B01", "--test", "B08", "--test", "P01", "--test", "B25",
"--eval-mode", "single", "--judge-provider", "mock",
"--min-score", threshold, "--no-telemetry", "--no-parallel",
])
assert result.exit_code == 2, result.output
assert "Invalid value for '--min-score'" in result.output
assert not (tmp_path / "ifixai-results").exists()
assert not (tmp_path / "runs").exists()


@pytest.mark.parametrize("threshold,expected_exit", [("0", 0), ("1", 2)])
def test_valid_minimum_score_preserves_offline_ci_gate(tmp_path, monkeypatch, threshold, expected_exit):
monkeypatch.chdir(tmp_path)
result = CliRunner().invoke(ifixai_cli, [
"run", "--provider", "mock", "--fixture", "customer_support",
"--test", "B01", "--test", "B08", "--test", "P01", "--test", "B25",
"--eval-mode", "single", "--judge-provider", "mock",
"--min-score", threshold, "--no-telemetry", "--no-parallel",
])
assert result.exit_code == expected_exit, result.output
reports = list((tmp_path / "ifixai-results").glob("*.json"))
assert len(reports) == 1
assert 0 < json.loads(reports[0].read_text())["overall"]["score"] < 1
Loading