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
9 changes: 9 additions & 0 deletions src/goodeye_cli/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,14 @@ class BudgetExhausted(GoodeyeError):
"""Caller's monthly credit budget has been exhausted (HTTP 402, slug=budget_exhausted)."""


class AnonymousDailyCapReached(GoodeyeError):
"""Shared daily cap on anonymous usage was reached (HTTP 402, slug=anonymous_daily_cap).

Distinct from BudgetExhausted: there is no account here, and the platform's
daily limit on total anonymous usage is reached. Sign in to use your own credits.
"""


class SafetyVerificationFailed(GoodeyeError):
"""Publish blocked: the hard-block safety rubric rejected the template body.

Expand Down Expand Up @@ -117,6 +125,7 @@ class SafetyVerificationUnavailable(GoodeyeError):
"internal_error": ServerError,
"account_suspended": AccountSuspended,
"budget_exhausted": BudgetExhausted,
"anonymous_daily_cap": AnonymousDailyCapReached,
"safety_verification_failed": SafetyVerificationFailed,
"safety_verification_unavailable": SafetyVerificationUnavailable,
}
Expand Down
17 changes: 17 additions & 0 deletions tests/test_commands_usage.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from goodeye_cli.config import ConfigPaths
from goodeye_cli.errors import (
AccountSuspended,
AnonymousDailyCapReached,
BudgetExhausted,
error_from_body,
)
Expand Down Expand Up @@ -170,6 +171,22 @@ def test_error_from_body_maps_budget_exhausted() -> None:
assert "exhausted" in err.message


def test_error_from_body_maps_anonymous_daily_cap() -> None:
err = error_from_body(
402,
{
"error": "anonymous_daily_cap",
"message": "Goodeye's anonymous usage limit for today has been reached.",
"resets_at": "2026-06-15T00:00:00+00:00",
},
)
assert isinstance(err, AnonymousDailyCapReached)
assert err.status_code == 402
assert err.slug == "anonymous_daily_cap"
assert "anonymous" in err.message.lower()
assert err.extras["resets_at"] == "2026-06-15T00:00:00+00:00"


@respx.mock
def test_usage_command_renders_account_suspended(
tmp_config_paths: ConfigPaths, monkeypatch
Expand Down