278 lines
9.7 KiB
Python
278 lines
9.7 KiB
Python
from __future__ import annotations
|
|
|
|
from datetime import UTC, datetime
|
|
import unittest
|
|
|
|
from sqlalchemy import create_engine
|
|
from sqlalchemy.orm import sessionmaker
|
|
|
|
from govoplan_connectors.backend.db.models import (
|
|
ConnectorConfiguration,
|
|
ConnectorKnowledgeObject,
|
|
ConnectorKnowledgeProfile,
|
|
ConnectorKnowledgeSyncRun,
|
|
ConnectorServiceDeskObject,
|
|
ConnectorServiceDeskProfile,
|
|
ConnectorServiceDeskSyncRun,
|
|
ConnectorSanctionsAcquisitionRun,
|
|
ConnectorSanctionsSnapshot,
|
|
ConnectorTabularSource,
|
|
)
|
|
from govoplan_connectors.backend.manifest import manifest
|
|
from govoplan_connectors.backend.provider_state import (
|
|
KNOWLEDGE_PROVIDER_ID,
|
|
SANCTIONS_PROVIDER_ID,
|
|
SERVICE_DESK_PROVIDER_ID,
|
|
TABULAR_PROVIDER_ID,
|
|
knowledge_provider_states,
|
|
sanctions_provider_states,
|
|
service_desk_provider_states,
|
|
tabular_provider_states,
|
|
)
|
|
from govoplan_core.core.provider_governance import ExternalProviderStateContext
|
|
from govoplan_core.db.base import Base
|
|
|
|
|
|
class ConnectorsProviderStateTests(unittest.TestCase):
|
|
def setUp(self) -> None:
|
|
self.engine = create_engine("sqlite+pysqlite:///:memory:", future=True)
|
|
Base.metadata.create_all(
|
|
self.engine,
|
|
)
|
|
self.session = sessionmaker(bind=self.engine, expire_on_commit=False)()
|
|
|
|
def tearDown(self) -> None:
|
|
self.session.close()
|
|
self.engine.dispose()
|
|
|
|
def test_immutable_tabular_snapshot_reports_ready_state(self) -> None:
|
|
self.session.add(
|
|
ConnectorTabularSource(
|
|
id="tabular-1",
|
|
tenant_id="tenant-1",
|
|
source_name="monthly",
|
|
name="Monthly input",
|
|
status="active",
|
|
schema_version=1,
|
|
schema_=[{"name": "id", "type": "string"}],
|
|
rows=[{"id": "1"}],
|
|
fingerprint="a" * 64,
|
|
row_count=1,
|
|
byte_count=10,
|
|
)
|
|
)
|
|
self.session.commit()
|
|
|
|
state = tabular_provider_states(
|
|
ExternalProviderStateContext(session=self.session, tenant_id="tenant-1")
|
|
)[0]
|
|
|
|
self.assertEqual("healthy", state.health)
|
|
self.assertEqual("ready", state.recovery)
|
|
self.assertEqual("not_applicable", state.freshness)
|
|
|
|
def test_sanctions_state_hashes_binding_and_manifest_registers_state(self) -> None:
|
|
now = datetime.now(UTC)
|
|
run = ConnectorSanctionsAcquisitionRun(
|
|
id="run-1",
|
|
tenant_id="tenant-1",
|
|
provider_id="eu",
|
|
source_id="secret-source-name",
|
|
status="succeeded",
|
|
attempt_count=1,
|
|
started_at=now,
|
|
finished_at=now,
|
|
)
|
|
snapshot = ConnectorSanctionsSnapshot(
|
|
id="snapshot-1",
|
|
tenant_id="tenant-1",
|
|
provider_id="eu",
|
|
publisher="European Union",
|
|
jurisdiction="EU",
|
|
list_type="sanctions",
|
|
source_id="secret-source-name",
|
|
source_version="2026-08-01",
|
|
acquired_at=now,
|
|
source_url="https://source.example.test/list.xml",
|
|
content_type="application/xml",
|
|
byte_count=8,
|
|
sha256="b" * 64,
|
|
parser_version="1",
|
|
connector_run_id=run.id,
|
|
raw_content=b"<list/>",
|
|
)
|
|
run.snapshot_id = snapshot.id
|
|
self.session.add_all((run, snapshot))
|
|
self.session.commit()
|
|
|
|
state = sanctions_provider_states(
|
|
ExternalProviderStateContext(session=self.session, tenant_id="tenant-1")
|
|
)[0]
|
|
|
|
self.assertEqual("healthy", state.health)
|
|
self.assertEqual("ready", state.recovery)
|
|
rendered = str(state.to_dict())
|
|
self.assertNotIn("secret-source-name", rendered)
|
|
self.assertNotIn("source.example.test", rendered)
|
|
self.assertEqual(
|
|
{
|
|
TABULAR_PROVIDER_ID,
|
|
SANCTIONS_PROVIDER_ID,
|
|
KNOWLEDGE_PROVIDER_ID,
|
|
SERVICE_DESK_PROVIDER_ID,
|
|
},
|
|
{item.provider_id for item in manifest.external_provider_state_providers},
|
|
)
|
|
|
|
def test_external_knowledge_state_reports_health_without_acl_or_endpoint_data(self) -> None:
|
|
now = datetime.now(UTC)
|
|
profile = ConnectorKnowledgeProfile(
|
|
id="knowledge-profile-1",
|
|
tenant_id="tenant-1",
|
|
configuration_id="configuration-secret",
|
|
status="active",
|
|
product="bluespice",
|
|
product_version="4.5.2",
|
|
desired_maturity="migrate",
|
|
discovered_maturity="migrate",
|
|
source_authority_mode="external_mirror",
|
|
default_visibility="restricted",
|
|
default_acl_tokens=["group:secret-acl"],
|
|
namespace_mappings=[{"secret": "mapping"}],
|
|
capabilities=["read", "synchronize", "migrate"],
|
|
health_status="healthy",
|
|
discovered_at=now,
|
|
)
|
|
page = ConnectorKnowledgeObject(
|
|
id="knowledge-object-1",
|
|
tenant_id="tenant-1",
|
|
profile_id=profile.id,
|
|
object_type="page",
|
|
external_id="42",
|
|
title="Secret page title",
|
|
status="active",
|
|
source_revision="501",
|
|
content_hash="c" * 64,
|
|
visibility="restricted",
|
|
acl_tokens=["group:secret-acl"],
|
|
observed_at=now,
|
|
)
|
|
run = ConnectorKnowledgeSyncRun(
|
|
id="knowledge-run-1",
|
|
tenant_id="tenant-1",
|
|
profile_id=profile.id,
|
|
mode="delta",
|
|
idempotency_key="secret-key",
|
|
request_hash="d" * 64,
|
|
status="completed",
|
|
started_at=now,
|
|
finished_at=now,
|
|
)
|
|
self.session.add_all((profile, page, run))
|
|
self.session.commit()
|
|
|
|
state = knowledge_provider_states(
|
|
ExternalProviderStateContext(session=self.session, tenant_id="tenant-1")
|
|
)[0]
|
|
|
|
self.assertEqual("healthy", state.health)
|
|
self.assertEqual("ready", state.recovery)
|
|
self.assertEqual(1, state.metrics["active_objects"])
|
|
rendered = str(state.to_dict())
|
|
self.assertNotIn("Secret page title", rendered)
|
|
self.assertNotIn("group:secret-acl", rendered)
|
|
self.assertNotIn("configuration-secret", rendered)
|
|
|
|
def test_service_desk_state_reports_recovery_without_ticket_or_acl_data(self) -> None:
|
|
now = datetime.now(UTC)
|
|
configuration = ConnectorConfiguration(
|
|
id="configuration-secret",
|
|
tenant_id="tenant-1",
|
|
definition_id="definition-1",
|
|
name="Secret service desk",
|
|
status="active",
|
|
base_definition_revision=1,
|
|
local_overrides={},
|
|
protected_paths=[],
|
|
effective_configuration={},
|
|
effective_hash="configuration-hash",
|
|
resource_revision=3,
|
|
ambiguity_policy="manual_review",
|
|
)
|
|
profile = ConnectorServiceDeskProfile(
|
|
id="service-desk-profile-1",
|
|
tenant_id="tenant-1",
|
|
configuration_id="configuration-secret",
|
|
status="active",
|
|
integration_mode="synchronize",
|
|
product="znuny",
|
|
product_version="7.1.4",
|
|
desired_maturity="synchronize",
|
|
discovered_maturity="synchronize",
|
|
source_authority_mode="governed_sync",
|
|
default_visibility="restricted",
|
|
default_acl_tokens=["group:secret-acl"],
|
|
routes={"secret": "route"},
|
|
queue_mappings=[{"secret": "queue"}],
|
|
dynamic_field_mappings=[{"secret": "field"}],
|
|
capabilities=["read", "search", "synchronize", "publish"],
|
|
discovered_configuration_revision=3,
|
|
discovered_configuration_hash="configuration-hash",
|
|
health_status="healthy",
|
|
discovered_at=now,
|
|
)
|
|
ticket = ConnectorServiceDeskObject(
|
|
id="service-desk-object-1",
|
|
tenant_id="tenant-1",
|
|
profile_id=profile.id,
|
|
object_type="ticket",
|
|
external_id="42",
|
|
external_ticket_number="secret-number",
|
|
title="Secret ticket title",
|
|
status="active",
|
|
source_revision="2026-08-22T10:00:00Z",
|
|
content_hash="e" * 64,
|
|
visibility="restricted",
|
|
acl_tokens=["group:secret-acl"],
|
|
mapped_data={"secret": "ticket content"},
|
|
provenance={"secret": "provider evidence"},
|
|
observed_at=now,
|
|
)
|
|
run = ConnectorServiceDeskSyncRun(
|
|
id="service-desk-run-1",
|
|
tenant_id="tenant-1",
|
|
profile_id=profile.id,
|
|
mode="update",
|
|
idempotency_key="secret-key",
|
|
request_hash="f" * 64,
|
|
status="outcome_unknown",
|
|
started_at=now,
|
|
finished_at=now,
|
|
)
|
|
self.session.add_all((configuration, profile, ticket, run))
|
|
self.session.commit()
|
|
|
|
state = service_desk_provider_states(
|
|
ExternalProviderStateContext(session=self.session, tenant_id="tenant-1")
|
|
)[0]
|
|
|
|
self.assertEqual(SERVICE_DESK_PROVIDER_ID, state.provider_id)
|
|
self.assertTrue(state.metrics["discovery_current"])
|
|
self.assertEqual("pending", state.conflict)
|
|
self.assertEqual("attention", state.recovery)
|
|
rendered = str(state.to_dict())
|
|
for secret in (
|
|
"configuration-secret",
|
|
"secret-number",
|
|
"Secret ticket title",
|
|
"group:secret-acl",
|
|
"ticket content",
|
|
"provider evidence",
|
|
"secret-key",
|
|
):
|
|
self.assertNotIn(secret, rendered)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|