diff --git a/pyoaev/apis/signature.py b/pyoaev/apis/signature.py index bed3e9b..ed8fdf2 100644 --- a/pyoaev/apis/signature.py +++ b/pyoaev/apis/signature.py @@ -1,6 +1,5 @@ """Signature callback API — transport layer for compiled signature payloads.""" -import json import logging import time from typing import Any @@ -10,7 +9,11 @@ from pyoaev import exceptions as exc from pyoaev.base import RESTManager, RESTObject from pyoaev.exceptions import SignatureTransmissionError -from pyoaev.signatures.models import SignatureCallbackPayload +from pyoaev.signatures.models import ( + ExecutionDetails, + SignatureCallbackPayload, + SignatureOutputStructure, +) class Signature(RESTObject): @@ -22,7 +25,7 @@ class Signature(RESTObject): class SignatureApiManager(RESTManager): """Manage signature callback transport to the OpenAEV backend. - Handles payload validation, auto-chunking, and retry with exponential backoff. + Handles payload validation and retry with exponential backoff. """ _path = "/injects" @@ -32,10 +35,6 @@ class SignatureApiManager(RESTManager): MAX_RETRIES = 3 RETRY_DELAYS = (1, 2, 4) - _CHUNK_METADATA_RESERVE = len( - ',"chunk_index":99999,"total_chunks":99999,"phase":"execution_complete_extended"' - ) - def __init__(self, openaev: "Any", parent: "Any" = None) -> None: """Initialize the signature API manager. @@ -68,8 +67,8 @@ def logger(self, value: logging.Logger) -> None: def send_signatures( self, inject_id: str, - phase: str, - signatures: dict[str, Any], + signatures: SignatureOutputStructure, + execution_details: ExecutionDetails, ) -> None: """Send compiled signatures to the inject callback endpoint. @@ -77,38 +76,35 @@ def send_signatures( Args: inject_id: Inject UUID. - phase: Execution phase (e.g. 'execution_complete'). signatures: Full signatures dict (canonical or flat, grouped on the fly). + execution_details: Raises: SignatureTransmissionError: Validation failed, 4xx hit, or retries exhausted. """ - self._logger.debug("send_signatures inject_id=%s phase=%s", inject_id, phase) - signatures = self._normalize_signature_payload(signatures) - payload = self._build_callback_payload(signatures, phase=phase) - - serialized = json.dumps(payload, separators=(",", ":")).encode() + self._logger.debug( + "send_signatures inject_id=%s, execution_status=%s, execution_action=%s", + inject_id, + execution_details.execution_status, + execution_details.execution_action, + ) + signatures = signatures.normalize_signature_payload() + payload = self._build_callback_payload( + signatures=signatures, execution_details=execution_details + ) - if len(serialized) <= self._max_payload_size: - self._send_with_retry(inject_id, payload) - else: - self._send_chunked(inject_id, payload["expectation_signature"], phase=phase) + self._send_with_retry(inject_id, payload) def _build_callback_payload( self, - signatures: dict[str, Any], - *, - phase: str | None = None, - chunk_index: int | None = None, - total_chunks: int | None = None, + signatures: SignatureOutputStructure, + execution_details: ExecutionDetails, ) -> dict[str, Any]: """Validate and wrap signatures in the strict callback envelope. Args: signatures: The inner signatures body, already normalised. - phase: Execution phase string (e.g. 'execution_complete'). - chunk_index: 0-based index when chunking, None for single POSTs. - total_chunks: Chunk count when chunking, None for single POSTs. + execution_details: The execution metadata to be stored next to the signatures in the payload. Returns: The validated dict ready for wire transmission. @@ -117,13 +113,8 @@ def _build_callback_payload( SignatureTransmissionError: Envelope failed Pydantic validation. """ try: - envelope = SignatureCallbackPayload.model_validate( - { - "expectation_signature": signatures, - "phase": phase, - "chunk_index": chunk_index, - "total_chunks": total_chunks, - } + envelope = SignatureCallbackPayload.build_from_models( + signatures, execution_details ) except ValidationError as ve: raise SignatureTransmissionError( @@ -131,137 +122,6 @@ def _build_callback_payload( ) from ve return envelope.model_dump(mode="json", exclude_none=True) - def _normalize_signature_payload( - self, signatures: dict[str, Any] - ) -> dict[str, Any]: - """Regroup signature_values by expectation_type within each target. - - Accepts flat or pre-grouped input and returns canonical grouped form. - - Args: - signatures: Raw signatures dict with any mix of flat and grouped entries. - - Returns: - New dict where every signature_values list is in canonical grouped form. - """ - targets = signatures.get("targets") - if not targets: - return signatures - - normalized_targets: list[dict[str, Any]] = [] - for target in targets: - sig_values = target.get("signature_values") - if not sig_values: - normalized_targets.append(target) - continue - - grouped: dict[str, list[dict[str, Any]]] = {} - order: list[str] = [] - - for entry in sig_values: - etype = entry.get("expectation_type") - if etype not in grouped: - grouped[etype] = [] - order.append(etype) - - if "values" in entry and isinstance(entry["values"], list): - grouped[etype].extend(entry["values"]) - else: - grouped[etype].append( - {k: v for k, v in entry.items() if k != "expectation_type"} - ) - - normalized_target = dict(target) - normalized_target["signature_values"] = [ - {"expectation_type": etype, "values": grouped[etype]} for etype in order - ] - normalized_targets.append(normalized_target) - - normalized = dict(signatures) - normalized["targets"] = normalized_targets - return normalized - - def _send_chunked( - self, inject_id: str, signatures: dict[str, Any], phase: str | None = None - ) -> None: - """Split targets across sequential POSTs, each tagged with chunk metadata. - - Args: - inject_id: Inject UUID for the callback path. - signatures: Normalised inner signatures body to partition. - phase: Execution phase forwarded to each chunk envelope. - - Raises: - SignatureTransmissionError: A single target alone exceeds max_payload_size. - """ - targets = signatures.get("targets", []) - if not targets: - payload = self._build_callback_payload(signatures, phase=phase) - size = len(json.dumps(payload, separators=(",", ":")).encode()) - if size > self._max_payload_size: - self._logger.warning( - "Payload of %d bytes exceeds max_payload_size %d but has no " - "'targets' key to chunk on; sending unchunked", - size, - self._max_payload_size, - ) - self._send_with_retry(inject_id, payload) - return - - budget = max(self._max_payload_size - self._CHUNK_METADATA_RESERVE, 0) - chunks: list[list[Any]] = [] - current_chunk: list[Any] = [] - - for target in targets: - candidate = current_chunk + [target] - size = len( - json.dumps( - {"expectation_signature": {"targets": candidate}}, - separators=(",", ":"), - ).encode() - ) - - if size <= budget: - current_chunk.append(target) - continue - - if not current_chunk: - raise SignatureTransmissionError( - error_message=( - f"Single target payload of {size} bytes exceeds " - f"max_payload_size {self._max_payload_size}; cannot chunk further" - ), - ) - - chunks.append(current_chunk) - current_chunk = [target] - solo_size = len( - json.dumps( - {"expectation_signature": {"targets": [target]}}, - separators=(",", ":"), - ).encode() - ) - if solo_size > budget: - raise SignatureTransmissionError( - error_message=( - f"Single target payload of {solo_size} bytes exceeds " - f"max_payload_size {self._max_payload_size}; cannot chunk further" - ), - ) - - if current_chunk: - chunks.append(current_chunk) - - total_chunks = len(chunks) - for idx, chunk_targets in enumerate(chunks): - chunk_payload = self._build_callback_payload( - {"targets": chunk_targets}, - phase=phase, - chunk_index=idx, - total_chunks=total_chunks, - ) - self._send_with_retry(inject_id, chunk_payload) - @exc.on_http_error(exc.OpenAEVUpdateError) def callback( self, inject_id: str, data: dict[str, Any], **kwargs: Any @@ -276,7 +136,7 @@ def callback( Returns: The parsed response from the backend. """ - path = f"{self.path}/{inject_id}/callback" + path = f"{self.path}/execution/callback/{inject_id}" result = self.openaev.http_post(path, post_data=data, **kwargs) return result diff --git a/pyoaev/signatures/__init__.py b/pyoaev/signatures/__init__.py index d8a60cd..dc2e061 100644 --- a/pyoaev/signatures/__init__.py +++ b/pyoaev/signatures/__init__.py @@ -1,31 +1,37 @@ from pyoaev.signatures.models import ( CloudInjectorConfig, ExpectationSignatureGroup, - ExternalInjectorConfig, ExtraSignatureData, InjectorConfig, NetworkInjectorConfig, SignatureCallbackPayload, SignaturePayload, + SignatureTarget, SignatureValue, TargetSignatures, build_network_configs, ) from pyoaev.signatures.signature_manager import SignatureManager -from pyoaev.signatures.types import ExpectationType, MatchTypes, SignatureTypes +from pyoaev.signatures.types import ( + ExpectationType, + InjectExecutionActions, + MatchTypes, + SignatureTypes, +) __all__ = [ "CloudInjectorConfig", "ExpectationSignatureGroup", - "ExternalInjectorConfig", "ExpectationType", "ExtraSignatureData", "InjectorConfig", + "InjectExecutionActions", "MatchTypes", "NetworkInjectorConfig", "SignatureCallbackPayload", "SignatureManager", "SignaturePayload", + "SignatureTarget", "SignatureTypes", "SignatureValue", "TargetSignatures", diff --git a/pyoaev/signatures/models.py b/pyoaev/signatures/models.py index 553c7e2..913d3f8 100644 --- a/pyoaev/signatures/models.py +++ b/pyoaev/signatures/models.py @@ -1,6 +1,8 @@ """Pydantic schemas pinning every shape SignatureManager touches.""" import ipaddress +from collections import defaultdict +from datetime import datetime, timezone from typing import Any from pydantic import ( @@ -8,11 +10,13 @@ ConfigDict, Field, JsonValue, + TypeAdapter, + computed_field, field_validator, model_validator, ) -from pyoaev.signatures.types import ExpectationType +from pyoaev.signatures.types import ExpectationType, InjectExecutionActions class SignatureValue(BaseModel): @@ -58,11 +62,22 @@ def get_extra(self, expectation_type: str): ) +class SignatureTarget(BaseModel): + """Target identity on the wire.""" + + model_config = ConfigDict(extra="forbid") + + agent: str | None = None + asset: str | None = None + asset_group: str | None = None + + class TargetSignatures(BaseModel): """A target plus everything observed about it, grouped by expectation.""" model_config = ConfigDict(extra="allow") + signature_target: SignatureTarget signature_values: list[ExpectationSignatureGroup] @@ -74,19 +89,101 @@ class SignaturePayload(BaseModel): targets: list[TargetSignatures] +class SignatureOutputStructure(BaseModel): + """Structured output to be serialized as a str in the callback payload yet data has to follow model.""" + + model_config = ConfigDict(populate_by_name=True, extra="forbid") + + signatures: SignaturePayload + + def normalize_signature_payload(self) -> None: + """ + Regroup signature_values by expectation_type within each target. + """ + normalized_targets: list[TargetSignatures] = [] + + for target in self.signatures.targets: + if not target.signature_values: + normalized_targets.append(target) + continue + + grouped: dict[str, list[dict[str, Any]]] = defaultdict(list) + order: list[str] = set() + + for entry in target.signature_values: + order.add(entry.expectation_type) + grouped[entry.expectation_type].extend(entry.values) + + normalized_target = TargetSignatures( + signature_target=target.signature_target, + signature_values=[ + ExpectationSignatureGroup( + expectation_type=expectation_type, + values=grouped[expectation_type], + ) + for expectation_type in order + ], + ) + + normalized_targets.append(normalized_target) + + self.signatures.targets = normalized_targets + + +class ExecutionDetails(BaseModel): + """Helper to wrap the execution-related details for the callback payload""" + + model_config = ConfigDict(extra="forbid") + + start_time: datetime = datetime.now(timezone.utc) + end_time: datetime | None = None + + execution_status: str + execution_message: str = "" + execution_action: InjectExecutionActions | None = None + + @computed_field + @property + def execution_duration(self) -> float: + try: + return (self.end_time - self.start_time).total_seconds() + except: + return 0.0 + + class SignatureCallbackPayload(BaseModel): """Outer POST envelope. Pure ``{signatures}`` when unchunked, plus chunk fields when split.""" model_config = ConfigDict(populate_by_name=True, extra="forbid") - expectation_signature: SignaturePayload - phase: str | None = None - chunk_index: int | None = None - total_chunks: int | None = None + execution_message: str + execution_output_structured: str | None = None + execution_status: str + execution_duration: int | None = None + execution_action: InjectExecutionActions | None = None + + @field_validator("execution_output_structured", mode="after") + @classmethod + def is_proper_signature_output_structure(cls, value: str) -> str: + TypeAdapter(SignatureOutputStructure).validate_json(value) + return value + + @classmethod + def build_from_models( + cls, signatures: SignatureOutputStructure, execution_details: ExecutionDetails + ): + """Producing a SignatureCallbackPayload from the data of a SignatureOutputStructure and of a ExecutionDetails.""" + return cls( + execution_message=execution_details.execution_message, + execution_output_structured=signatures.model_dump_json(exclude_none=True), + execution_status=execution_details.execution_status, + execution_duration=execution_details.execution_duration, + execution_action=execution_details.execution_action, + ) class PreExecutionSignature(BaseModel): - """Pre-execution data dump. Field set varies by category: network, cloud, external.""" + """Pre-execution data dump. Field set varies by category: network, cloud.""" model_config = ConfigDict(extra="allow") @@ -106,9 +203,6 @@ class PreExecutionSignature(BaseModel): cloud_region: str | None = None target_service: str | None = None - # External - query: str | None = None - class PostExecutionSignature(PreExecutionSignature): """Post-execution view: pre-execution fields plus outcome, end_time, and any partial results.""" @@ -159,7 +253,7 @@ class NetworkInjectorConfig(BaseModel): def check_one(cls, data): assert ( sum( - value != None + value is not None for key, value in data.items() if key in ["target_ipv4", "target_ipv6", "target_hostname"] ) @@ -179,17 +273,7 @@ class CloudInjectorConfig(BaseModel): target_service: str | None = None -class ExternalInjectorConfig(BaseModel): - """A single external scan target (e.g. Shodan): a query against an asset.""" - - model_config = ConfigDict(extra="forbid") - - query: str - target_ipv4: str | None = None - target_hostname: str | None = None - - -InjectorConfig = NetworkInjectorConfig | CloudInjectorConfig | ExternalInjectorConfig +InjectorConfig = NetworkInjectorConfig | CloudInjectorConfig # --------------------------------------------------------------------------- @@ -245,6 +329,7 @@ def build_network_configs( __all__ = [ "SignatureValue", "ExpectationSignatureGroup", + "SignatureTarget", "TargetSignatures", "SignaturePayload", "SignatureCallbackPayload", @@ -255,7 +340,6 @@ def build_network_configs( "ToolOutput", "NetworkInjectorConfig", "CloudInjectorConfig", - "ExternalInjectorConfig", "InjectorConfig", "build_network_configs", ] diff --git a/pyoaev/signatures/signature_manager.py b/pyoaev/signatures/signature_manager.py index aaf101d..f0feb82 100644 --- a/pyoaev/signatures/signature_manager.py +++ b/pyoaev/signatures/signature_manager.py @@ -13,13 +13,13 @@ from pyoaev.signatures.models import ( CloudInjectorConfig, ExpectationSignatureGroup, - ExternalInjectorConfig, ExtraSignatureData, InjectorConfig, NetworkInjectorConfig, PostExecutionSignature, PreExecutionSignature, SignaturePayload, + SignatureTarget, SignatureValue, TargetSignatures, ToolOutput, @@ -57,8 +57,8 @@ def compile_pre_execution_signatures( """Build pre-execution signature dicts from one or more typed injector configs. The category is carried by the config type itself - (:class:`NetworkInjectorConfig`, :class:`CloudInjectorConfig`, - :class:`ExternalInjectorConfig`), so no separate ``category`` flag is needed. + (:class:`NetworkInjectorConfig`, :class:`CloudInjectorConfig`), + so no separate ``category`` flag is needed. Args: config: A single injector config or a homogeneous list of them. @@ -95,7 +95,7 @@ def _compile_one(self, config: InjectorConfig, start_time: str) -> dict[str, Any Common pipeline for every category: 1. Seed the base dict with ``start_time`` and category-specific context - (network gets resolved source IPs; cloud/external add nothing). + (network gets resolved source IPs; cloud add nothing). 2. Layer the config's own fields on top. 3. Run it through :class:`PreExecutionSignature` for validation and emit JSON-ready output stripped of ``None``\\ s. @@ -109,14 +109,14 @@ def _source_context(self, config: InjectorConfig) -> dict[str, Any]: """Return the source identity bits injected for the config's category. Only network signatures need the running container's source IPs; - cloud and external rows have no source identity to carry. + cloud rows have no source identity to carry. """ if isinstance(config, NetworkInjectorConfig): return { "source_ipv4": self.resolve_container_ip(), "source_ipv6": self._cached_ipv6, } - if isinstance(config, (CloudInjectorConfig, ExternalInjectorConfig)): + if isinstance(config, CloudInjectorConfig): return {} raise TypeError(f"unsupported injector config type: {type(config).__name__}") @@ -173,6 +173,7 @@ def _merge_post( @staticmethod def build_payload( post_signatures: dict[str, Any] | list[dict[str, Any]], + targets_meta: dict[str, str] | list[dict[str, str]], expectation_types: list[str], extra_signatures: ExtraSignatureData | None = None, ) -> dict[str, Any]: @@ -183,6 +184,7 @@ def build_payload( Args: post_signatures: A single post-execution dict or a list (multi-targets). + targets_meta: Target metadata dict(s) with keys like agent, asset, asset_group. expectation_types: The 1+ expectation type labels (e.g. ['DETECTION', 'PREVENTION']). extra_signatures: Optional mapping of expectation types to additional signature fields that will be merged into the base post_signatures. @@ -192,23 +194,19 @@ def build_payload( """ if isinstance(post_signatures, dict): post_signatures = [post_signatures] + if isinstance(targets_meta, dict): + targets_meta = [targets_meta] * len(post_signatures) targets = [] - for signature in post_signatures: + for signature, target in zip(post_signatures, targets_meta): signature_values = [] - for expectation_type in expectation_types: signature_data = signature.copy() signature_data.update(extra_signatures.get_extra(expectation_type)) - values = [ - SignatureValue( - signature_type=key, - signature_value=value, - ) + SignatureValue(signature_type=key, signature_value=value) for key, value in signature_data.items() ] - signature_values.append( ExpectationSignatureGroup( expectation_type=expectation_type, @@ -217,6 +215,7 @@ def build_payload( ) targets.append( TargetSignatures( + signature_target=SignatureTarget(**target), signature_values=signature_values, ) ) diff --git a/pyoaev/signatures/types.py b/pyoaev/signatures/types.py index ca2737d..0c4f78b 100644 --- a/pyoaev/signatures/types.py +++ b/pyoaev/signatures/types.py @@ -7,6 +7,17 @@ class ExpectationType(str, Enum): VULNERABILITY = "VULNERABILITY" +class InjectExecutionActions(str, Enum): + PREREQUISITE_CHECK = "prerequisite_check" + PREREQUISITE_EXECUTION = "prerequisite_execution" + CLEANUP_EXECUTION = "cleanup_execution" + COMMAND_EXECUTION = "command_execution" + DNS_RESOLUTION = "dns_resolution" + FILE_EXECUTION = "file_execution" + FILE_DROP = "file_drop" + COMPLETE = "complete" + + class MatchTypes(str, Enum): MATCH_TYPE_FUZZY = "fuzzy" MATCH_TYPE_SIMPLE = "simple" diff --git a/test/signatures/constraints/signature_manager_transmission_constraints.feature b/test/signatures/constraints/signature_manager_transmission_constraints.feature index 0fcf4f1..5a596c2 100644 --- a/test/signatures/constraints/signature_manager_transmission_constraints.feature +++ b/test/signatures/constraints/signature_manager_transmission_constraints.feature @@ -10,7 +10,7 @@ Feature: SignatureManager transmission constraints Given a compiled payload whose serialised size exceeds MAX_PAYLOAD_SIZE by at least a factor of 2 And the backend responds with HTTP 200 When I call send_signatures for inject_id "inject-abc-001" with phase "execution_complete" - Then the payload is sent as multiple sequential POST requests to /injects/inject-abc-001/callback + Then the payload is sent as multiple sequential POST requests to /injects/execution/callback/inject-abc-001 And each POST request body contains chunk_index as a 0-based integer And each POST request body contains total_chunks as a positive integer matching the total number of chunks sent And each POST request body contains only "signatures", "chunk_index" and "total_chunks" at the top level @@ -21,7 +21,7 @@ Feature: SignatureManager transmission constraints Given a compiled post-execution payload for inject_id "inject-abc-001" And the backend responds with HTTP 503 on every attempt When I call send_signatures for inject_id "inject-abc-001" with phase "execution_complete" - Then send_signatures sends a total of 4 POST requests to /injects/inject-abc-001/callback + Then send_signatures sends a total of 4 POST requests to /injects/execution/callback/inject-abc-001 And a WARNING log message containing the retry attempt number is emitted before each of the 3 retry attempts And the wait before attempt 2 is 1 second And the wait before attempt 3 is 2 seconds @@ -32,7 +32,7 @@ Feature: SignatureManager transmission constraints Given a compiled post-execution payload for inject_id "inject-abc-001" And the backend responds with HTTP 400 and body '{"error": "bad request"}' When I call send_signatures for inject_id "inject-abc-001" with phase "execution_complete" - Then only 1 POST request is sent to /injects/inject-abc-001/callback + Then only 1 POST request is sent to /injects/execution/callback/inject-abc-001 And an ERROR log message containing status code 400 and the response body is emitted And an exception is raised immediately And no sleep or wait occurs before the exception is raised diff --git a/test/signatures/features/signature_manager_pre_execution.feature b/test/signatures/features/signature_manager_pre_execution.feature index 1ba3ed5..1ae80da 100644 --- a/test/signatures/features/signature_manager_pre_execution.feature +++ b/test/signatures/features/signature_manager_pre_execution.feature @@ -40,14 +40,6 @@ Feature: SignatureManager pre-execution signature compilation And the returned dict does not contain target_ipv4 And the returned dict does not contain target_ipv6 - Scenario: External category returns scan target fields and no source IP - Given an ExternalInjectorConfig with target_ipv4="203.0.113.5" and query="port:22 os:linux" - When I call compile_pre_execution_signatures with the config - Then the returned dict contains target_ipv4 equal to "203.0.113.5" - And the returned dict contains query equal to "port:22 os:linux" - And the returned dict contains start_time as a UTC ISO 8601 string - But the returned dict does not contain source_ipv4 - Scenario Outline: Network multi-target returns one dict per target with a shared source IP Given a list of 3 NetworkInjectorConfig with target_ipv4 "10.0.0.1", "10.0.0.2", "10.0.0.3" And the running container has a resolvable IPv4 address "172.17.0.2" diff --git a/test/signatures/features/signature_manager_transmission.feature b/test/signatures/features/signature_manager_transmission.feature index 94e34bd..2d228cb 100644 --- a/test/signatures/features/signature_manager_transmission.feature +++ b/test/signatures/features/signature_manager_transmission.feature @@ -21,7 +21,7 @@ Feature: SignatureManager signature transmission and container IP resolution Given a compiled payload with 1 target, expectation_type "DETECTION", signature_type "public_ip", signature_value "203.0.113.5" And the backend responds with HTTP 200 When I call send_signatures for inject_id "inject-abc-001" with phase "execution_complete" - Then a POST request is sent to /injects/inject-abc-001/callback + Then a POST request is sent to /injects/execution/callback/inject-abc-001 And the POST request body contains signatures.targets as a list And signatures.targets[0].signature_values[0].expectation_type equals "DETECTION" And signatures.targets[0].signature_values[0].values[0].signature_type equals "public_ip" diff --git a/test/signatures/test_signature_manager_pre_execution.py b/test/signatures/test_signature_manager_pre_execution.py index f7581dc..93db6cc 100644 --- a/test/signatures/test_signature_manager_pre_execution.py +++ b/test/signatures/test_signature_manager_pre_execution.py @@ -7,7 +7,6 @@ from pyoaev.signatures.models import ( CloudInjectorConfig, - ExternalInjectorConfig, NetworkInjectorConfig, build_network_configs, ) @@ -42,14 +41,6 @@ def test_cloud_category_required_fields(): pass -@scenario( - "features/signature_manager_pre_execution.feature", - "External category returns scan target fields and no source IP", -) -def test_external_category_fields(): - pass - - @scenario( "features/signature_manager_pre_execution.feature", "Network multi-target returns one dict per target with a shared source IP", @@ -161,16 +152,6 @@ def cloud_config_single( ) -@given( - parsers.parse( - 'an ExternalInjectorConfig with target_ipv4="{target_ipv4}" and query="{query}"' - ), - target_fixture="config", -) -def external_config_single(target_ipv4, query): - return ExternalInjectorConfig(target_ipv4=target_ipv4, query=query) - - @given( parsers.parse( "a list of 3 NetworkInjectorConfig with target_ipv4 " diff --git a/test/signatures/test_signature_manager_transmission.py b/test/signatures/test_signature_manager_transmission.py index 6519bd6..416e572 100644 --- a/test/signatures/test_signature_manager_transmission.py +++ b/test/signatures/test_signature_manager_transmission.py @@ -379,12 +379,15 @@ def send_signatures_completes_without_exception(context): @then( parsers.parse( - "a POST request is sent to /injects/{inject_id}/callback", + "a POST request is sent to /injects/execution/callback/{inject_id}", ) ) def assert_post_request_sent_to_callback(context, inject_id): assert context["captured_calls"] - assert context["captured_calls"][-1]["path"] == f"/injects/{inject_id}/callback" + assert ( + context["captured_calls"][-1]["path"] + == f"/injects/execution/callback/{inject_id}" + ) @then("the POST request body contains signatures.targets as a list") @@ -443,14 +446,14 @@ def assert_signature_target_key(context): @then( parsers.parse( - "the payload is sent as multiple sequential POST requests to /injects/{inject_id}/callback", + "the payload is sent as multiple sequential POST requests to /injects/execution/callback/{inject_id}", ) ) def assert_payload_sent_as_multiple_chunks(context, inject_id): assert context["send_exception"] is None assert len(context["captured_calls"]) > 1 assert all( - call_item["path"] == f"/injects/{inject_id}/callback" + call_item["path"] == f"/injects/execution/callback/{inject_id}" for call_item in context["captured_calls"] ) @@ -515,13 +518,13 @@ def assert_payload_size_per_chunk(context): @then( parsers.parse( - "send_signatures sends a total of {total_requests:d} POST requests to /injects/{inject_id}/callback" + "send_signatures sends a total of {total_requests:d} POST requests to /injects/execution/callback/{inject_id}" ) ) def assert_total_post_requests(context, total_requests, inject_id): assert len(context["captured_calls"]) == total_requests assert all( - call_item["path"] == f"/injects/{inject_id}/callback" + call_item["path"] == f"/injects/execution/callback/{inject_id}" for call_item in context["captured_calls"] ) @@ -553,12 +556,15 @@ def assert_signature_transmission_error_after_retries(context): @then( parsers.parse( - "only {request_count:d} POST request is sent to /injects/{inject_id}/callback" + "only {request_count:d} POST request is sent to /injects/execution/callback/{inject_id}" ) ) def assert_single_post_request(context, request_count, inject_id): assert len(context["captured_calls"]) == request_count - assert context["captured_calls"][0]["path"] == f"/injects/{inject_id}/callback" + assert ( + context["captured_calls"][0]["path"] + == f"/injects/execution/callback/{inject_id}" + ) @then(