Testing#

Running Tests#

pip install -e ".[test]"
pytest -v

Tests run automatically via GitHub Actions on every push and pull request to main (Python 3.10–3.13). See .github/workflows/tests.yml.

Test Strategy#

The test suite focuses on logic that can break in non-obvious ways: resolution order, parsing, formatting, and protocol handling. Service classes (chat, voice, arcana) are thin HTTP wrappers — their correctness is best verified by integration tests against the real API, not by mocking requests.

Tests are deliberately kept minimal. Each test either:

  • protects a priority ordering that could silently regress,

  • validates parsing of a real-world format variant, or

  • has already caught a bug during development.

Test Inventory#

test_auth.py — API Key Discovery#

Test

Purpose

test_explicit_path_raw

.saia_api file loads correctly

test_explicit_path_dotenv

.env file loads correctly (with quotes)

test_env_var

SAIA_API_KEY env var resolves

test_env_var_stripped

Whitespace around env var value is stripped

test_env_var_empty_skipped

Empty SAIA_API_KEY="" falls through to file

test_saia_api_file_in_cwd

.saia_api in cwd is found

test_saia_api_file_skips_comments

Lines starting with # are ignored

test_dotenv_file_in_cwd

.env in cwd is parsed, other keys ignored

test_dotenv_strips_quotes

Single and double quotes stripped from values

test_nothing_found_raises

ValueError with helpful message when no key found

test_env_var_beats_file

Env var takes priority over .saia_api file

test_saia_api_beats_dotenv

.saia_api takes priority over .env

test_auth.py — ARCANA ID Priority Chain#

Test

Purpose

test_priority_1_env_saia_arcana_id

SAIA_ARCANA_ID env var beats config.toml

test_priority_2_toml_default

[saia.arcana] default beats ids array

test_priority_3_toml_ids_first

First ids element becomes default when no explicit default

test_priority_4_numbered_env_first

First SAIA_ARCANA_ID_XX becomes default as last resort

test_env_default_beats_toml_default

.env SAIA_ARCANA_ID beats config.toml default

test_auth.py — ARCANA ID Source Merging#

Test

Purpose

test_toml_ids_array

[saia.arcana] ids parsed as indexed dict entries

test_toml_labels

[saia.arcana.labels] parsed as named entries

test_numbered_env_vars

SAIA_ARCANA_ID_XX env vars collected by suffix

test_empty_returns_empty_dict

No configuration returns {}

test_all_sources_merge

All sources (env, toml ids, toml labels, numbered) merge into one dict

test_auth.py — Legacy Removal#

Test

Purpose

test_legacy_arcana_id_ignored

ARCANA_ID (without SAIA_ prefix) is no longer accepted

test_auth.py — Owner Prefix Resolution#

Test

Purpose

test_username_set_id_without_prefix

Username configured, ID has no / → username auto-prepended

test_no_username_id_with_prefix

No username, ID already has / → works as-is

test_both_username_and_prefix

Username set and ID has / → ID left unchanged

test_no_username_no_prefix_raises

No username and no / in ID → ValueError raised

test_auth.py — extract_arcana_name#

Test

Purpose

test_full_id_strips_owner

owner/namename (the bug that caused a 404)

test_owner_with_multiple_slashes

Only the first / is treated as the separator

test_name_with_spaces

Spaces in arcana names are preserved

test_auth.py — Username and Summary#

Test

Purpose

test_from_toml

Username loaded from [saia] username in config.toml

test_env_beats_toml

Env var SAIA_USERNAME overrides config.toml

test_none_when_missing

Returns None when no username configured

test_summary_output

arcana.summary() includes configured IDs, labels, and server data

test_rate_limits.py — Header Parsing and Formatting#

Test

Purpose

test_full_headers

All 9 rate-limit headers map to correct dataclass fields

test_non_integer_values_ignored

Non-numeric header values are silently skipped

test_full_output_alignment

/ and ( columns are aligned across all rows

test_remaining_none_shows_question_mark

None remaining displays ? (caught a real TypeError)

test_streaming.py — SSE Line Parsing#

Test

Purpose

test_normal_chunks

Standard data: {...} lines yield parsed JSON

test_done_terminates

Generator stops at data: [DONE] and does not yield after

test_malformed_json_skipped

Invalid JSON lines are silently skipped

test_no_space_after_data_colon

data:{...} (no space) is handled correctly

test_error_response_raises

HTTP errors raise before iteration begins

test_exceptions.py — Error Handling#

Test

Purpose

test_429_includes_parsed_rate_limits

RateLimitError includes parsed rate_limits attribute from headers