Test suite: close breaking-change gaps (CI gating, cross-platform runs, contract & log-contract layers)
メンテナーはふだん 2 日以内に返信
まだ誰も着手していません。
評価
- 難易度
- 4/5
- 見積もり時間
- 3〜5日
- 初心者へのやさしさ
- 52/100
- issue の種類
- バグ
- 明瞭さ
- おおむね明確
- 活発さ
- 静か
- 技術スタック
- docker, github-actions, postgresql, python
- 領域
- testing-qa, tooling
調査の方向性
.github/workflows/check_python.yml から始めて、detect が現在 unit-tests と integration-tests をどのように制御しているかを確認します。次に、tests/unit/utils/test_conf_path.py、tests/integration/conftest.py、pyproject.toml を確認し、Windows パスと Docker の可用性に関する具体的な失敗、および pytest 設定の不足を調べます。ユニットスイートを実行してから、提案されたファイルに従って tests/contract/ と tests/unit/observability/ にコントラクトテストとオブザーバビリティテストを追加または検証します。CI が設定変更時にスキップされなくなり、Docker なしでインテグレーションのコレクションが問題なくスキップされ、新しいテストによって route/schema/sql/auth のコントラクト整合性が強制されれば完了です。
索引モデルが issue の本文から書いたものです。
説明
Description of Technical Debt
The automated test suite is broad (235 unit tests, 45 integration tests, 94% line coverage on src/) and the integration layer is genuinely good — real Postgres 16 and Kafka via testcontainers, moto for S3/Secrets Manager/EventBridge, an in-process mock JWT provider, and the Lambda handlers invoked through real API Gateway proxy events.
What it does not currently guarantee is the thing we actually want from it: that a breaking change to existing functionality fails CI. There are three structural holes and a set of narrower coverage gaps.
The suite as measured today (local run, master):
| Layer | Tests | Result |
|---|---|---|
Unit (tests/unit/) |
237 | 235 passed, 2 failed on Windows |
Integration (tests/integration/) |
45 | 45 passed in 26 s |
Coverage on src/ |
— | 94% (lowest file: writer_postgres.py at 79%) |
Structural holes
H1 — CI runs no tests at all when non-Python files change.
.github/workflows/check_python.yml gates every job (including unit-tests and integration-tests) behind the detect job, which only looks for changed *.py and requirements*.txt files. A PR that touches only conf/topic_schemas/*.json, conf/access.json, conf/config.json, conf/topic_keys.json, api.yaml, src/**/sql/*.sql, or Dockerfile hits the noop job and merges green.
Those are precisely the highest-risk breaking-change surfaces:
- tightening
requiredin a topic schema → existing producers start getting400 - editing
src/writers/sql/inserts.sqlnamed parameters →WriterPostgresbreaks at runtime - editing
access.json→ silent403for a tenant
H2 — Two unit tests fail on Windows, so make qa cannot pass locally.
tests/unit/utils/test_conf_path.py hardcodes the POSIX separator:
assert conf_dir.endswith("pkg/conf") # line 82
assert conf_dir.endswith("pkg_invalid_current/conf") # line 121
On Windows resolve_conf_dir() returns ...\pkg\conf, so both fail. CI is ubuntu-latest and never sees it. Fix by comparing Path objects or os.path.join("pkg", "conf").
H3 — The integration suite hard-errors instead of skipping when Docker is unavailable.
tests/integration/conftest.py::_prepull_images is scope="session", autouse=True and calls docker.from_env(timeout=300) unconditionally. With no Docker daemon this raises DockerException during collection and the entire suite errors out — make qa gives a stack trace rather than a skip. Contributors without Docker have no usable local QA path.
Coverage gaps
G1 — WriterPostgres._upsert_status_change has no unit test.
writer_postgres.py lines 171–189 (the whole event_type → created_at/started_at/finished_at mapping for JobCreatedEvent, JobCreatedAndStartedEvent, JobStartedEvent, JobFinishedEvent) is exercised only by tests/integration/test_status_change_writer.py. Combined with H1 and H3 that means the newest writer (added in #189) has no regression net on a config-only PR or on a machine without Docker.
G2 — Nothing enforces api.yaml ↔ ROUTE_MAP consistency.
Both currently list the same 8 routes, but they are maintained by hand on both sides. /docs, /stats/{topic_name} and /terminate were each added twice, manually. Drift is undetected until a consumer reads the spec.
G3 — Nothing enforces topic JSON Schema ↔ Postgres writer field access.
_insert_dlchange, _insert_run and _insert_test use direct subscript access for required fields (message["catalog_id"], message["job_ref"], job["status"], …). If a field is dropped from required in the topic schema, validation passes and the writer then raises KeyError → 500. This is a config-only change, so per H1 it also runs no tests.
G4 — The Postgres DDL exists only inside test code.
tests/integration/schemas/postgres_schema.py::SCHEMA_SQL is the only schema definition in the repository; the authoritative production DDL lives elsewhere. Integration tests can pass against a table shape that no longer matches production.
G5 — Nothing verifies the deployable artifact.
The Dockerfile flattens src/, conf/ and api.yaml into ${LAMBDA_TASK_ROOT}. conf_path.py has a dedicated resolution branch (scenario 3) for exactly that layout, but it is only tested against synthetic temp directories. No test builds the image and confirms src.event_gate_lambda.lambda_handler imports and answers /health.
G6 — Coverage gates are inconsistent and far below actual.
- CI:
pytest --cov=. -v tests/unit/ --cov-fail-under=80 Makefile:pytest tests/unit/ --cov=src --cov-fail-under=90- Actual: 94%
The effective ratchet permits a 14-point regression. There is also no per-file floor, so writer_postgres.py at 79% hides behind the aggregate, and no coverage report is published on the PR.
G7 — No pytest configuration.
pyproject.toml has no [tool.pytest.ini_options]: no testpaths, no registered markers (so there is no -m "not integration" escape hatch), and no filterwarnings. The suite already emits, in four integration modules:
PytestRemovedIn10Warning: Class-scoped fixture defined as instance method is deprecated.
That is a silent break waiting for the next pytest major bump.
G8 — Auth negative tests are thin.
Covered: expired token, wrong signing key, missing token, unauthorized sub, case-insensitive sub match. Not covered: alg=none, an HS256 token signed with the RSA public key (algorithm confusion), a token with no sub claim, a non-string sub, and Authorization values with embedded whitespace/newlines. decode_jwt pins algorithms=["RS256"] so these should all be rejected — which is exactly why they deserve regression tests.
G9 — Repository hygiene.
.coverageis tracked in git despite being listed in.gitignore(PR #204 happens to delete it).tests/integration/.tmp_conf/is written bylambda_handler_factoryand only removed if it ends up empty; it is not in.gitignore.
Interaction with PR #204 (structured logging)
#204 (feature/193-improve-service-logging) is well tested for its own surface and should not be blocked by this issue. It adds tests/unit/utils/test_observability.py (13 tests, including correlation-id validation, cold-start flipping and request-scoped-key leakage between warm invocations), tests/unit/utils/test_logging_levels.py, 9 new test_utils.py tests for the rewritten dispatch_request, and a TestCorrelationId class in the integration health tests.
Two things it changes are worth folding into this work rather than leaving implicit:
dispatch_requestnow catches bareExceptionat the boundary instead of a tuple of six exception types. That is the right call, but it means any handler bug now silently becomes a logged500instead of surfacing. The log-contract layer proposed below is what keeps that from becoming a blind spot.HandlerTopic.handle_requestgained JSON-body validation (400for a non-JSON or non-object body) andresolve_request_topicgained a400for a missingtopic_namepath parameter. These are new externally visible status codes thatapi.yamldoes not document — G2 would catch that.
Impact of Technical Debt
- A config-only PR can break production with a fully green CI run. This is the concrete, current risk (H1).
- Contributors on Windows cannot run
make qa(H2), and contributors without Docker get a crash rather than a skip (H3) — both push verification onto CI, which per H1 may not run. - The newest writer (
status_change, #189) has the weakest unit coverage of any module (G1). - Schema/spec/SQL drift is invisible until runtime (G2, G3, G4).
- 94% line coverage overstates confidence: the gate is set at 80, and line coverage says nothing about assertion strength.
Category
Testing / Test Coverage
Priority
Medium - Should be addressed soon
Proposed Solution
Ordered by value per unit of effort.
1. Make CI actually run (H1) — highest value, smallest change
In check_python.yml, split the concerns:
- Keep the
detectgate forpylint-analysis,black-checkandmypy-check(they genuinely only care about*.py). - Run
unit-testsunconditionally on every PR. It takes ~10 s. - Extend the detection glob for
integration-teststo include the behavioural surfaces:
--jq '.[].filename | select(
endswith(".py") or endswith(".sql") or endswith(".yaml") or
(startswith("conf/")) or (startswith("requirements")) or
. == "Dockerfile" or . == "api.yaml"
)'
2. Fix cross-platform and no-Docker execution (H2, H3)
test_conf_path.py: replaceendswith("pkg/conf")withPath(conf_dir) == module_dir / "conf".tests/integration/conftest.py: probe the daemon once and skip cleanly.
@pytest.fixture(scope="session", autouse=True)
def _prepull_images() -> None:
try:
client = docker.from_env(timeout=300)
client.ping()
except docker.errors.DockerException as exc:
pytest.skip(f"Docker is not available, skipping integration tests: {exc}", allow_module_level=True)
...
Add tests/integration/.tmp_conf/ to .gitignore and git rm --cached .coverage.
3. Add a contract test layer — new, fast, no Docker required
A new tests/contract/ package that runs on every PR in well under a second and covers exactly the drift H1 leaves open:
test_api_spec_matches_routes.py— parseapi.yamlpaths, assert set equality againstevent_gate_lambda.ROUTE_MAP∪event_stats_lambda.ROUTE_MAP; assert every status code a handler can return is documented (this immediately catches the new400s from #204).test_schema_matches_writer.py— for each topic, assert every keyWriterPostgresaccesses via subscript is present in the schema'srequiredarray.test_sql_params_match_writer.py— extract%(name)splaceholders fromsrc/writers/sql/inserts.sqlandsrc/readers/sql/stats.sql, assert they equal the dict keys passed by the writer/reader.test_config_consistency.py— assertconstants.TOPIC_*≡ files inconf/topic_schemas/≡ keys inaccess.json, and that everytopic_keys.jsonkey is a known topic and every value is a field defined in that topic's schema.
4. Add a log-contract test layer — protects PR #204's value
Under tests/unit/observability/, drive the handler with caplog/captured stdout and assert:
- every emitted record is valid JSON with
service,level,message,correlation_id,cold_start,resource; - no record ever contains a raw bearer token, a password, or a Secrets Manager payload;
- no request-scoped key (
user,topic) survives from aPOST /topics/{topic}into a followingGET /healthon the same warm container — the integration-level counterpart to the unit test #204 already has; LOG_LEVEL=TRACEproduces redacted, size-capped payloads (safe_serialize_for_logis well unit-tested; this checks the wiring).
5. Close the coverage gaps
- Parametrized unit tests for
_upsert_status_changeover all fourevent_typevalues plus an unknown one, asserting the exactcreated_at/started_at/finished_attriple (G1). - Auth negative tests:
alg=none, HS256-signed-with-public-key, missingsub, non-stringsub(G8). - Align both gates on
--cov=src --cov-fail-under=93, add--cov-report=xmlplus a PR coverage comment, and add a per-file floor so no module drops below ~85% (G6). - Add
[tool.pytest.ini_options]withtestpaths, registeredunit/integration/contractmarkers,--strict-markers, andfilterwarnings = ["error::DeprecationWarning", ...]; fix the class-scoped-fixture deprecation it surfaces (G7).
6. Optional / follow-up layers
- Container smoke job —
docker buildthe Lambda image, run it under the AWS Runtime Interface Emulator, and assert/healthand/topicsrespond. Catches packaging andconf_pathflattening breaks that no unit test can (G5). - Golden payload corpus —
tests/fixtures/payloads/<topic>/*.jsonof historically accepted messages, replayed through validation and the writers. Any schema tightening then fails a test instead of a producer in production. This is the direct answer to "catch breaking changes over existing functionality". - Vendor the authoritative DDL into
db/schema.sqland have the integration fixture load that file instead ofSCHEMA_SQL(G4). - Mutation testing (
mutmut) oversrc/handlers/andsrc/utils/, nightly and non-blocking. 94% line coverage says nothing about assertion strength; this measures it.
Runner and local-environment considerations
- Unit + contract tests: no Docker, no network, run everywhere in ~10 s. Safe to make unconditional on PRs.
- Integration tests: keep
ubuntu-latest; the existing parallel pre-pull with backoff already handles registry flakiness, and the 15-minute timeout is generous against a 26 s local run. Do not addpytest-xdist— the container fixtures are session-scoped and would need reworking first. - After the H2/H3 fixes,
make qapasses on Windows, macOS and Linux, with or without Docker.
Effort Estimate
~3–4 days total: ~0.5 day for H1–H3 and the hygiene fixes, ~1.5 days for the contract and log-contract layers, ~1 day for the coverage gaps and pytest/CI configuration. The container smoke job, golden corpus, DDL vendoring and mutation testing are a separate follow-up of similar size.
Dependencies / Related
- #204 — structured logging via Powertools (in review). Not blocked by this issue; the log-contract layer in step 4 and the
api.yamlstatus-code check in step 3 are the natural follow-ups to it. - #189 — aggregated Postgres writer for the status change topic (the module with the weakest unit coverage, G1).
- #193 — service logging improvements.
- 主要言語
- Python
- スター
- 4
- フォーク
- 0
- 平均マージ
- 3日 7時間
- マージ済み PR(30日)
- 11
環境構築
- Dockerfile または Docker Compose ファイルあり
- プルリクエストのテンプレートあり
- コントリビューションガイドなし
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
AbsaOSS/EventGate のほかの issue
-
refactoring type:tech-debt
難易度 2/5 1〜3時間 初心者へのやさしさ 84/100
メンテナーはふだん 2 日以内に返信
-
enhancement
難易度 2/5 1〜3時間 初心者へのやさしさ 70/100
メンテナーはふだん 2 日以内に返信
-
bug
難易度 3/5 1〜2日 初心者へのやさしさ 64/100
メンテナーはふだん 2 日以内に返信
-
enhancement
難易度 5/5 1週間以上 初心者へのやさしさ 32/100
メンテナーはふだん 2 日以内に返信
-
infrastructure type:tech-debt
難易度 3/5 1〜2日 初心者へのやさしさ 70/100
メンテナーはふだん 2 日以内に返信
AbsaOSS/EventGate の issue をすべて見る
似ている issue
-
難易度 2/5 1〜3時間 初心者へのやさしさ 85/100
メンテナーはふだん 1 日以内に返信
-
SR_SECURITY_DESCRIPTOR.fromString drops the SACL when no DACL is present対応中かも @paul7436 が今日担当しました。 オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 88/100
メンテナーはふだん 2 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 85/100
equinor/fmu-sumo-uploader#302 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 75/100
modelscope/evalscope#1821 ·
メンテナーはふだん 1 日以内に返信
-
Sanity on ansible-core devel fails: ignore-2.23.txt references the removed import-3.9 test対応中かも @yurnov が今日担当しました。 オープンneeds_triage
難易度 1/5 1時間未満 初心者へのやさしさ 91/100
ansible-collections/kubernetes.core#1275 ·
メンテナーはふだん 1 日以内に返信