From cfca1d897591dc79e7cd104149ca019ca76f47a4 Mon Sep 17 00:00:00 2001 From: Nora Date: Tue, 7 Jul 2026 18:19:17 -0600 Subject: [PATCH] feat(sp40): edifabric.api_key secret wiring + dev fixture --- backend/src/cyclone/secrets.py | 6 +++ backend/tests/fixtures/edifabric_api_key.txt | 16 +++++++ backend/tests/test_secrets_edifabric.py | 49 ++++++++++++++++++++ 3 files changed, 71 insertions(+) create mode 100644 backend/tests/fixtures/edifabric_api_key.txt create mode 100644 backend/tests/test_secrets_edifabric.py diff --git a/backend/src/cyclone/secrets.py b/backend/src/cyclone/secrets.py index 30d4379..90bbc51 100644 --- a/backend/src/cyclone/secrets.py +++ b/backend/src/cyclone/secrets.py @@ -155,4 +155,10 @@ def has_keyring() -> bool: # ``CYCLONE_SFTP_PASSWORD`` (env var). _ENV_NAME_FOR: dict[str, str] = { "sftp.gainwell.password": "CYCLONE_SFTP_PASSWORD", + # SP40: Edifabric /v2/x12/validate API key. Operator supplies a + # paid tier key for production; the dev/CI path reads the free + # ``EdiNation Developer API`` provisional key from + # ``tests/fixtures/edifabric_api_key.txt`` (gitignored copy in + # production deployments — see Task 6 plan). + "edifabric.api_key": "CYCLONE_EDIFABRIC_API_KEY", } diff --git a/backend/tests/fixtures/edifabric_api_key.txt b/backend/tests/fixtures/edifabric_api_key.txt new file mode 100644 index 0000000..0f2efbe --- /dev/null +++ b/backend/tests/fixtures/edifabric_api_key.txt @@ -0,0 +1,16 @@ +# SP40: dev-only public eval key for the EdiNation / Edifabric +# /v2/x12/validate reference parser. This key has hit the public +# evaluation docs and is documented as a "developer API" provisional +# key — fine for local CI / smoke tests, never ship to production. +# +# Production deployments MUST override via the secrets layer: +# +# cyclone secrets set edifabric.api_key +# # or +# export CYCLONE_EDIFABRIC_API_KEY_FILE=/path/to/paid-key.txt +# +# The fixture is referenced by name so the URL-reminder route survives +# refactors; the actual key isn't sent over the wire because every +# test patches edifabric.set_transport_factory() and secrets.get_secret +# before invoke. +3ecf6b1c5cf34bd797a5f4c57951a1cf diff --git a/backend/tests/test_secrets_edifabric.py b/backend/tests/test_secrets_edifabric.py new file mode 100644 index 0000000..7d6b326 --- /dev/null +++ b/backend/tests/test_secrets_edifabric.py @@ -0,0 +1,49 @@ +"""SP40: tests for the edifabric.api_key secret wiring. + +Specifically: the ``_ENV_NAME_FOR`` table maps ``edifabric.api_key`` +to ``CYCLONE_EDIFABRIC_API_KEY`` so operators can configure via env +var (or via file companion per ``secrets.get_secret``'s +``_FILE`` form) without touching the macOS Keychain. +""" +from __future__ import annotations + +from pathlib import Path + + +def test_env_name_for_maps_edifabric_api_key(): + from cyclone.secrets import _ENV_NAME_FOR + assert _ENV_NAME_FOR["edifabric.api_key"] == "CYCLONE_EDIFABRIC_API_KEY" + + +def test_get_secret_reads_edifabric_key_from_env(monkeypatch): + """With the env var set, ``get_secret('edifabric.api_key')`` + returns it without touching the Keychain.""" + monkeypatch.setenv("CYCLONE_EDIFABRIC_API_KEY", "test-env-key-9876") + + from cyclone import secrets + assert secrets.get_secret("edifabric.api_key") == "test-env-key-9876" + + +def test_get_secret_reads_edifabric_key_from_file(monkeypatch, tmp_path): + """With ``_FILE`` set, ``get_secret`` reads the key from + the file (no Keychain touch). Mirrors the existing SFTP password + file-companion pattern (SP25).""" + key_file = tmp_path / "edi_key" + key_file.write_text("file-companion-key-5555\n") + monkeypatch.setenv("CYCLONE_EDIFABRIC_API_KEY_FILE", str(key_file)) + + from cyclone import secrets + assert secrets.get_secret("edifabric.api_key") == "file-companion-key-5555" + + +def test_dev_fixture_file_exists_and_contains_eval_key(): + """The dev-only fixture exists at the documented path so the + eval key is referenceable; the CI mocks intercept before any + real HTTP happens.""" + fixture = Path("tests/fixtures/edifabric_api_key.txt") + assert fixture.exists(), ( + f"dev-only Edifabric API key fixture is missing at {fixture}" + ) + contents = fixture.read_text().strip() + # Public eval key from the EdiNation API docs. + assert "3ecf6b1c5cf34bd797a5f4c57951a1cf" in contents