Wires decnet-ttp as a first-class worker: * `decnet ttp` CLI command (master-only via MASTER_ONLY_COMMANDS) * deploy/decnet-ttp.service.j2 systemd unit (After= identity / intel / reuse-correlator workers; ProtectHome=read-only since FilesystemRuleStore only reads ./rules/ttp/) * deploy/decnet.target Wants= chain extended with decnet-ttp.service * `ttp` was already in web/worker_registry.KNOWN_WORKERS tests/api/test_schemathesis_ttp.py: TTP-routes-only schemathesis suite, filtered via the OpenAPI tags=["TTP Tagging"] annotation shared by the eight TTP routes. Reuses the live uvicorn subprocess the wider test_schemathesis spawns; max_examples=400 keeps the focused gate fast for E.3.13–E.3.16 iteration. wiki-checkout/Service-Bus.md committed in its own repo: ttp.tagged and ttp.rule.fired.<id> flipped from "reserved (TTP worker)" to "decnet.ttp.worker" now that the worker publishes them.
88 lines
2.9 KiB
Python
88 lines
2.9 KiB
Python
"""Schemathesis contract tests scoped to the TTP Tagging API surface.
|
||
|
||
E.3.17 of ``development/TTP_TAGGING.md``. The full ``test_schemathesis``
|
||
suite fuzzes every endpoint with ``max_examples=3000`` — slow and
|
||
overkill when iterating on TTP-routes-only changes (E.3.13–E.3.16).
|
||
This file filters by the OpenAPI ``tags=["TTP Tagging"]`` annotation
|
||
the eight TTP routes carry, runs against the same live uvicorn
|
||
subprocess the wider suite spins up, and applies the same check
|
||
battery so a 4xx-shape regression on a TTP route fails here without
|
||
waiting on the rest of the API.
|
||
|
||
Routes covered (all decorated ``tags=["TTP Tagging"]``):
|
||
|
||
* ``GET /api/v1/ttp/techniques``
|
||
* ``GET /api/v1/ttp/by-identity/{identity_uuid}``
|
||
* ``GET /api/v1/ttp/by-attacker/{attacker_uuid}``
|
||
* ``GET /api/v1/ttp/by-campaign/{campaign_uuid}``
|
||
* ``GET /api/v1/ttp/by-session/{session_id}``
|
||
* ``GET /api/v1/ttp/rules``
|
||
* ``POST /api/v1/ttp/rules/{rule_id}/state``
|
||
* ``DELETE /api/v1/ttp/rules/{rule_id}/state``
|
||
* ``GET /api/v1/ttp/export/navigator``
|
||
* ``GET /api/v1/ttp/export/navigator/identity/{identity_uuid}``
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
import pytest
|
||
import schemathesis as st
|
||
from hypothesis import HealthCheck, Verbosity, settings
|
||
|
||
from tests.api.test_schemathesis import (
|
||
ALL_CHECKS,
|
||
AUTH_CHECKS,
|
||
LIVE_SERVER_URL,
|
||
before_call as _shared_before_call, # noqa: F401 (registers @st.hook)
|
||
)
|
||
|
||
# Reuse the schema fetched against the same uvicorn subprocess started
|
||
# by ``test_schemathesis``. Filtering by tag keeps the TTP suite a
|
||
# fast, focused contract gate without re-spinning the server.
|
||
TTP_SCHEMA = st.openapi.from_url(
|
||
f"{LIVE_SERVER_URL}/openapi.json",
|
||
).include(tag="TTP Tagging")
|
||
|
||
|
||
@pytest.mark.fuzz
|
||
@TTP_SCHEMA.parametrize()
|
||
@settings(
|
||
max_examples=400,
|
||
deadline=None,
|
||
verbosity=Verbosity.normal,
|
||
suppress_health_check=[
|
||
HealthCheck.filter_too_much,
|
||
HealthCheck.too_slow,
|
||
HealthCheck.data_too_large,
|
||
],
|
||
)
|
||
def test_ttp_schema_compliance(case):
|
||
"""Per-TTP-route schema compliance — valid + invalid inputs."""
|
||
case.call_and_validate(checks=ALL_CHECKS)
|
||
|
||
|
||
@pytest.mark.fuzz
|
||
@TTP_SCHEMA.parametrize()
|
||
@settings(
|
||
max_examples=120,
|
||
deadline=None,
|
||
verbosity=Verbosity.normal,
|
||
suppress_health_check=[
|
||
HealthCheck.filter_too_much,
|
||
HealthCheck.too_slow,
|
||
],
|
||
)
|
||
def test_ttp_auth_enforcement(case):
|
||
"""Every TTP route rejects requests without a Bearer token (401).
|
||
|
||
The mutation endpoints additionally require ``admin`` (server-side
|
||
``require_admin``); the authless probe doesn't distinguish 401 vs
|
||
403 here — the ``ignored_auth`` check just asserts that an absent
|
||
token never lands the request inside the handler with a usable
|
||
identity.
|
||
"""
|
||
case.headers = {
|
||
k: v for k, v in (case.headers or {}).items()
|
||
if k.lower() != "authorization"
|
||
}
|
||
case.call_and_validate(checks=AUTH_CHECKS)
|