Skip to content

Commit 00dbe79

Browse files
kurtseifriedclaude
andauthored
ci: add Python smoke tests and CI workflow (#5)
First CI for this repo. Adds smoke tests covering the testable modules (resolver, registry_loader, storage) plus a Python-matrix workflow that runs them on every PR and push to main. Smoke test coverage: - All three core modules import cleanly without side effects - SECID_TYPES is the canonical 10-name list - PR #4's dedup is preserved (resolver.SECID_TYPES IS registry_loader.SECID_TYPES) - resolve() handles empty and prefix-less inputs without raising Deliberately does NOT import secid_server.py — it runs argparse + storage initialization at import time, which makes it untestable without restructuring. A future PR will move its CLI bootstrap behind an `if __name__ == "__main__":` guard so it can be imported and tested. Matrix: Python 3.10, 3.11, 3.12, 3.13 (3.9 dropped — fastapi requires 3.10+). Security: no untrusted input interpolated; matrix values are literals. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent d0d664d commit 00dbe79

2 files changed

Lines changed: 127 additions & 0 deletions

File tree

.github/workflows/test.yml

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
name: Test
2+
3+
# Runs the test suite for the self-hosted Python resolver.
4+
#
5+
# Currently covers smoke tests only — verifying imports, the canonical
6+
# 10-type list, and basic resolve() invariants. Fuller coverage (parser,
7+
# resolver semantics, integration with a real registry) will land as the
8+
# conformance suite work in SecID-Client-SDK matures and gets ported.
9+
#
10+
# TypeScript and Go implementations are README-stubs today; their CI jobs
11+
# will be added when those implementations land.
12+
13+
on:
14+
pull_request:
15+
push:
16+
branches: [main]
17+
workflow_dispatch:
18+
19+
permissions:
20+
contents: read
21+
22+
jobs:
23+
python:
24+
name: Python
25+
runs-on: ubuntu-latest
26+
defaults:
27+
run:
28+
working-directory: python
29+
strategy:
30+
matrix:
31+
python-version: ["3.10", "3.11", "3.12", "3.13"]
32+
steps:
33+
- uses: actions/checkout@v4
34+
- name: Setup Python ${{ matrix.python-version }}
35+
uses: actions/setup-python@v5
36+
with:
37+
python-version: ${{ matrix.python-version }}
38+
- name: Install dependencies
39+
run: |
40+
pip install -r requirements.txt
41+
pip install pytest
42+
- name: Run smoke tests
43+
run: pytest test_smoke.py -v

python/test_smoke.py

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
"""Smoke tests — verify the core resolver modules import cleanly and basic
2+
constants/behavior are sane.
3+
4+
This is intentionally minimal: import-level guarantees plus a couple of
5+
sanity assertions. Full test coverage will come with the conformance suite
6+
work tracked in SecID-Client-SDK.
7+
8+
Note: deliberately does NOT import secid_server.py — that module runs
9+
argparse + storage initialization at import time, which makes it untestable
10+
without restructuring. A future PR will move its CLI bootstrap into a
11+
main() function gated by `if __name__ == "__main__":`.
12+
"""
13+
14+
import pytest
15+
16+
17+
def test_resolver_module_imports():
18+
"""resolver.py is the core resolution logic; must import without side effects."""
19+
from resolver import resolve, SECID_TYPES
20+
assert callable(resolve)
21+
assert isinstance(SECID_TYPES, list)
22+
23+
24+
def test_registry_loader_module_imports():
25+
"""registry_loader.py owns SECID_TYPES and the bulk_load function."""
26+
from registry_loader import SECID_TYPES, bulk_load
27+
assert callable(bulk_load)
28+
assert isinstance(SECID_TYPES, list)
29+
30+
31+
def test_storage_module_imports():
32+
"""storage.py provides the pluggable Store abstraction."""
33+
from storage import create_store, Store
34+
assert callable(create_store)
35+
36+
37+
def test_secid_types_canonical():
38+
"""The 10 official SecID types — frozen at v1.0, must not drift silently."""
39+
from registry_loader import SECID_TYPES
40+
expected = {
41+
"advisory", "capability", "control", "disclosure", "entity",
42+
"methodology", "reference", "regulation", "ttp", "weakness",
43+
}
44+
assert set(SECID_TYPES) == expected, (
45+
f"Type list drift detected. "
46+
f"Missing: {expected - set(SECID_TYPES)}. "
47+
f"Extra: {set(SECID_TYPES) - expected}."
48+
)
49+
assert len(SECID_TYPES) == 10
50+
51+
52+
def test_secid_types_single_source():
53+
"""Confirm resolver.py imports SECID_TYPES from registry_loader rather
54+
than redefining it (PR #4 dedup)."""
55+
import resolver
56+
import registry_loader
57+
assert resolver.SECID_TYPES is registry_loader.SECID_TYPES, (
58+
"resolver.SECID_TYPES should be the same object as "
59+
"registry_loader.SECID_TYPES (imported, not redefined). "
60+
"If they differ, the dedup from PR #4 has regressed."
61+
)
62+
63+
64+
def test_resolve_handles_empty_input():
65+
"""resolve() must not crash on edge inputs — minimum contract."""
66+
from resolver import resolve
67+
from storage import create_store
68+
69+
store = create_store("memory")
70+
result = resolve(store, "")
71+
assert isinstance(result, dict)
72+
# Empty input should produce an error envelope, not raise.
73+
assert "secid_query" in result or "error" in result or "results" in result
74+
75+
76+
def test_resolve_handles_missing_prefix():
77+
"""A SecID without the 'secid:' prefix is malformed; must return an error envelope."""
78+
from resolver import resolve
79+
from storage import create_store
80+
81+
store = create_store("memory")
82+
result = resolve(store, "advisory/mitre.org/cve#CVE-2021-44228")
83+
assert isinstance(result, dict)
84+
# Should produce SOME response, not raise.

0 commit comments

Comments
 (0)