Skip to content

spanforge.testing_mocks — Mock Service Clients

DX-003 · Phase 12 · Added in v2.0.11

Zero-network mock replacements for all 11 SpanForge SDK service clients. Every mock records calls, supports custom return values, and installs/restores automatically via mock_all_services().


Installation

No extra install required — testing_mocks is part of the core spanforge package:

from spanforge.testing_mocks import mock_all_services

mock_all_services()

@contextmanager
def mock_all_services() -> Generator[dict[str, _MockBase], None, None]:

Context manager that patches all 11 singleton service clients in spanforge.sdk with mock instances. On exit the original clients are restored.

from spanforge.testing_mocks import mock_all_services

def test_pipeline():
    with mock_all_services() as mocks:
        run_pipeline()
        mocks["sf_pii"].assert_called("scan")
        mocks["sf_audit"].assert_called("append")

Returned dict keys

KeyMock ClassReal Client
sf_identityMockSFIdentitySFIdentityClient
sf_piiMockSFPIISFPIIClient
sf_secretsMockSFSecretsSFSecretsClient
sf_auditMockSFAuditSFAuditClient
sf_cecMockSFCECSFCECClient
sf_observeMockSFObserveSFObserveClient
sf_alertMockSFAlertSFAlertClient
sf_gateMockSFGateSFGateClient
sf_enterpriseMockSFEnterpriseSFEnterpriseClient
sf_trustMockSFTrustSFTrustClient
sf_securityMockSFSecuritySFSecurityClient

_MockBase — Base Class

All mock clients inherit from _MockBase, which provides:

Properties & Methods

MemberSignatureDescription
.callsdict[str, list[tuple]]Recorded calls per method name.
.call_count()call_count(method: str) -> intNumber of times method was called.
.assert_called()assert_called(method: str)Raise AssertionError if method was never called.
.assert_not_called()assert_not_called(method: str)Raise AssertionError if method was called.
.configure_response()configure_response(method: str, response: Any)Set a custom return value for all future calls.
.reset()reset()Clear all recorded calls and configured responses.

Mock Classes

MockSFIdentity

Replaces SFIdentityClient.

MethodDefault Return
issue_api_key(**kwargs)APIKeyBundle(api_key="sf_test_mock_key_...", ...)
verify_token(jwt)JWTClaims(subject="mock-subject", scopes=["*"], ...)
revoke_key(key_id)None
rotate_key(key_id)APIKeyBundle(...)
create_session(api_key)"mock.session.jwt"
refresh_token()"mock.refreshed.jwt"
introspect(token)TokenIntrospectionResult(active=True, ...)
get_status(){"status": "ok", "mode": "local", ...}

MockSFPII

Replaces SFPIIClient.

MethodDefault Return
scan(payload, **kwargs)SFPIIScanResult(hits=[], scanned=1)
scan_text(text, **kwargs)PIITextScanResult(entities=[], detected=False, ...)
redact(event, **kwargs)SFPIIRedactResult(event=event, redaction_count=0, ...)
contains_pii(event, **kwargs)False
anonymize(text, **kwargs)SFPIIAnonymizeResult(text=text, replacements=0, ...)
scan_batch(texts, **kwargs)[PIITextScanResult(...) for t in texts]
get_status()PIIStatusInfo(status="ok", ...)

MockSFSecrets

Replaces SFSecretsClient.

MethodDefault Return
scan(text, **kwargs)None
scan_batch(texts, **kwargs)[]
get_status(){"status": "ok", "patterns_loaded": 0}

MockSFAudit

Replaces SFAuditClient.

MethodDefault Return
append(record, schema_key, **kwargs)AuditAppendResult(record_id="mock-record-id", ...)
sign(record)SignedRecord(record_id="mock-id", ...)
verify_chain(records, **kwargs){"valid": True, "gaps": []}
export(schema_key, **kwargs)[]
get_trust_scorecard(project_id, **kwargs)None
get_status()AuditStatusInfo(status="ok", ...)
close()None

MockSFCEC

Replaces SFCECClient.

MethodDefault Return
build_bundle(project_id, date_range, **kwargs)BundleResult(bundle_id="mock-bundle", zip_path="mock.zip", ...)
verify_bundle(zip_path)BundleVerificationResult(overall_valid=True, ...)
generate_dpa(project_id, controller_details, processor_details, **kwargs)DPADocument(document_id="mock-dpa", ...)
get_status()CECStatusInfo(status="ok", ...)

MockSFObserve

Replaces SFObserveClient.

MethodDefault Return
emit_span(name, attributes, **kwargs){"span_id": "mock-span-id"}
add_annotation(event_type, payload, **kwargs)"mock-annotation-id"
get_annotations(event_type, from_dt, to_dt, **kwargs)[]
export_spans(spans, **kwargs)ExportResult(exported_count=len(spans), backend="mock", ...)
healthy (property)True
last_export_at (property)None

MockSFAlert

Replaces SFAlertClient.

MethodDefault Return
publish(topic, payload, **kwargs)PublishResult(alert_id="mock-alert-id", routed_to=[], suppressed=False)
register_topic(topic, description, **kwargs)None
acknowledge(alert_id)True
get_alert_history(**kwargs)[]
get_status()AlertStatusInfo(status="ok", healthy=True, ...)
healthy (property)True

MockSFGate

Replaces SFGateClient.

MethodDefault Return
evaluate(gate_id, payload, **kwargs)GateEvaluationResult(gate_id=gate_id, verdict="PASS", ...)
run_trust_gate(project_id, **kwargs)TrustGateResult(verdict="PASS", pass_=True, ...)
evaluate_prri(project_id, **kwargs)PRRIResult(verdict="GREEN", prri_score=95, allow=True, ...)
list_artifacts(gate_id, **kwargs)[]
get_status()GateStatusInfo(status="ok", ...)

MockSFEnterprise

Replaces SFEnterpriseClient.

MethodDefault Return
register_tenant(project_id, org_id, **kwargs)TenantConfig(project_id=..., org_id=..., ...)
get_tenant(project_id)None
list_tenants()[]
get_isolation_scope(project_id)IsolationScope(org_id="mock-org", ...)
get_endpoint_for_project(project_id)"http://localhost:8080"
configure_encryption(**kwargs)EncryptionConfig()
get_status()EnterpriseStatusInfo(status="ok")

MockSFTrust

Replaces SFTrustClient.

MethodDefault Return
get_scorecard(project_id, **kwargs)TrustScorecardResponse(overall_score=1.0, colour_band="green", ...)
get_history(project_id, **kwargs)[]
get_badge(project_id)TrustBadgeResult(overall=1.0, colour_band="green", svg="<svg/>", ...)
get_status()TrustStatusInfo(status="ok", ...)

MockSFSecurity

Replaces SFSecurityClient.

MethodDefault Return
run_owasp_audit(**kwargs)SecurityAuditResult(categories={}, pass_=True, ...)
get_threat_model(service)[]
add_threat(service, category, threat, mitigation, risk_level)ThreatModelEntry(...)
scan_dependencies(**kwargs)[]
audit_logs_for_secrets(log_lines)0
run_full_scan(**kwargs)SecurityScanResult(vulnerabilities=[], pass_=True, ...)
get_status(){"status": "ok"}

Examples

Assert service interactions

from spanforge.testing_mocks import mock_all_services

def test_audit_trail():
    with mock_all_services() as mocks:
        process_llm_request("What is AI?")

        mocks["sf_pii"].assert_called("scan")
        mocks["sf_audit"].assert_called("append")
        assert mocks["sf_observe"].call_count("emit_span") == 1
        mocks["sf_alert"].assert_not_called("publish")

Custom responses for error paths

def test_gate_blocks_high_risk():
    with mock_all_services() as mocks:
        mocks["sf_gate"].configure_response("evaluate", {
            "verdict": "FAIL",
            "message": "Hallucination score too high",
        })

        result = run_pipeline()
        assert result.blocked is True

Reset between sub-tests

def test_multiple_scenarios():
    with mock_all_services() as mocks:
        run_scenario_a()
        assert mocks["sf_pii"].call_count("scan") == 2

        mocks["sf_pii"].reset()

        run_scenario_b()
        assert mocks["sf_pii"].call_count("scan") == 1

See Also