agnes-the-ai-analyst/tests/helpers/factories.py
ZdenekSrotyr b7a1795834
feat(scheduler): re-wire sync_schedule + script.schedule; tune via env; OpenMetadata TLS (#135)
Bundles 4 issues:
- #79 — table_registry.sync_schedule honored at runtime (API-side filter + Pydantic validators)
- #78 — script_registry.schedule honored via new POST /api/scripts/run-due (atomic claim, BackgroundTask exec, deploy-time safety validation)
- #77 — sidecar JOBS env-driven (SCHEDULER_DATA_REFRESH_INTERVAL/HEALTH_CHECK_INTERVAL/SCRIPT_RUN_INTERVAL/TICK_SECONDS)
- #89 — OpenMetadataClient verify=True default (BREAKING for self-signed)

Cuts release 0.19.0. See CHANGELOG for full notes incl. Known Limitations.
2026-04-29 22:06:30 +02:00

148 lines
5.8 KiB
Python

"""Faker-based test data factories with deterministic seed."""
import hashlib
import hmac
import json
import uuid
from typing import Any
from faker import Faker
Faker.seed(42)
_fake = Faker()
class UserFactory:
"""Factory for user dicts matching UserRepository.create() signature."""
@staticmethod
def build(role: str = "analyst", **overrides) -> dict[str, Any]:
data = {
"id": str(uuid.uuid4()),
"email": _fake.unique.email(),
"name": _fake.name(),
"role": role,
}
data.update(overrides)
return data
class TableRegistryFactory:
"""Factory for table_registry entry dicts."""
_SOURCE_TYPES = ["keboola", "bigquery", "csv"]
_QUERY_MODES = ["local", "remote"]
_SCHEDULES = ["every 1h", "daily 06:00", "every 30m"]
@staticmethod
def build(**overrides) -> dict[str, Any]:
source_type = overrides.pop("source_type", _fake.random_element(TableRegistryFactory._SOURCE_TYPES))
data = {
"name": _fake.unique.slug().replace("-", "_"),
"source_type": source_type,
"bucket": f"in.c-{_fake.word()}",
"source_table": _fake.word() + "_data",
"query_mode": _fake.random_element(TableRegistryFactory._QUERY_MODES),
"sync_schedule": _fake.random_element(TableRegistryFactory._SCHEDULES),
"description": _fake.sentence(),
}
data["source_type"] = source_type
data.update(overrides)
return data
class KnowledgeItemFactory:
"""Factory for knowledge item dicts."""
_CATEGORIES = ["business", "technical", "process", "metrics"]
@staticmethod
def build(**overrides) -> dict[str, Any]:
data = {
"title": _fake.sentence(nb_words=6).rstrip("."),
"content": _fake.paragraph(nb_sentences=4),
"category": _fake.random_element(KnowledgeItemFactory._CATEGORIES),
"tags": [_fake.word() for _ in range(_fake.random_int(1, 4))],
}
data.update(overrides)
return data
class WebhookEventFactory:
"""Factory for webhook event payloads."""
@staticmethod
def build_jira_event(
event_type: str = "jira:issue_updated",
issue_key: str | None = None,
**overrides,
) -> dict[str, Any]:
if issue_key is None:
project = _fake.lexify("????").upper()
issue_key = f"{project}-{_fake.random_int(1, 9999)}"
project_key = issue_key.split("-")[0]
payload: dict[str, Any] = {
"webhookEvent": event_type,
"timestamp": _fake.unix_time() * 1000,
"issue": {
"id": str(_fake.random_int(10000, 99999)),
"key": issue_key,
"self": f"https://jira.example.com/rest/api/2/issue/{issue_key}",
"fields": {
"summary": _fake.sentence(nb_words=8).rstrip("."),
"status": {"name": _fake.random_element(["To Do", "In Progress", "Done"]), "id": str(_fake.random_int(1, 10))},
"issuetype": {"name": _fake.random_element(["Bug", "Story", "Task", "Epic"]), "id": str(_fake.random_int(1, 10))},
"priority": {"name": _fake.random_element(["Low", "Medium", "High", "Critical"])},
"project": {"key": project_key, "name": f"{project_key} Project", "id": str(_fake.random_int(10000, 99999))},
"created": _fake.iso8601(),
"updated": _fake.iso8601(),
},
},
"user": {"displayName": _fake.name(), "emailAddress": _fake.email()},
}
payload.update(overrides)
return payload
@staticmethod
def sign_payload(payload: dict[str, Any], secret: str) -> str:
"""Sign payload with HMAC-SHA256. Uses default json.dumps() to match production."""
body = json.dumps(payload).encode()
sig = hmac.new(secret.encode(), body, hashlib.sha256).hexdigest()
return f"sha256={sig}"
# Convenience methods used by Block D connector tests
@staticmethod
def issue_updated(issue_key: str = "PROJ-123", webhook_secret: str = "") -> tuple[dict, bytes, str]:
event_data = {
"webhookEvent": "jira:issue_updated",
"issue": {"key": issue_key, "id": "10001", "fields": {"summary": "Test issue", "status": {"name": "Open"}, "issuetype": {"name": "Bug"}}},
"user": {"displayName": "Test User"},
}
payload = json.dumps(event_data).encode()
sig = WebhookEventFactory._sign(payload, webhook_secret) if webhook_secret else ""
return event_data, payload, sig
@staticmethod
def issue_deleted(issue_key: str = "PROJ-456", webhook_secret: str = "") -> tuple[dict, bytes, str]:
event_data = {
"webhookEvent": "jira:issue_deleted",
"issue": {"key": issue_key, "id": "10002", "fields": {"summary": "Deleted issue"}},
}
payload = json.dumps(event_data).encode()
sig = WebhookEventFactory._sign(payload, webhook_secret) if webhook_secret else ""
return event_data, payload, sig
@staticmethod
def issue_created(issue_key: str = "PROJ-789", webhook_secret: str = "") -> tuple[dict, bytes, str]:
event_data = {
"webhookEvent": "jira:issue_created",
"issue": {"key": issue_key, "id": "10003", "fields": {"summary": "New issue", "status": {"name": "Open"}, "issuetype": {"name": "Story"}}},
}
payload = json.dumps(event_data).encode()
sig = WebhookEventFactory._sign(payload, webhook_secret) if webhook_secret else ""
return event_data, payload, sig
@staticmethod
def _sign(payload: bytes, secret: str) -> str:
mac = hmac.new(secret.encode("utf-8"), payload, hashlib.sha256).hexdigest()
return f"sha256={mac}"