Organized
This commit is contained in:
@@ -0,0 +1,178 @@
|
||||
"""End-to-end tests for the Redmine -> CRM sync pipeline."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
import responses
|
||||
|
||||
import contact_enrichment
|
||||
import sync_redmine_contacts
|
||||
from redmine_crm.crm_client import CrmClient
|
||||
from redmine_crm.redmine_client import RedmineClient
|
||||
|
||||
BASE_URL = "https://redmine.example.com"
|
||||
|
||||
|
||||
@responses.activate
|
||||
def test_pipeline_full_run_creates_contact():
|
||||
contacts = [
|
||||
{
|
||||
"id": 1,
|
||||
"first_name": "Grace",
|
||||
"last_name": "Hopper",
|
||||
"email": "grace@example.com",
|
||||
"updated_at": "2026-07-04T12:00:00Z",
|
||||
}
|
||||
]
|
||||
responses.add(
|
||||
responses.GET,
|
||||
f"{BASE_URL}/contacts.json",
|
||||
json={"contacts": contacts, "total_count": 1},
|
||||
status=200,
|
||||
)
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
state_file = Path(tmpdir) / "state.json"
|
||||
redmine = RedmineClient(BASE_URL, "secret", project_id=1)
|
||||
crm = CrmClient()
|
||||
crm._store.clear()
|
||||
state = sync_redmine_contacts.FileSyncState(state_file)
|
||||
config = contact_enrichment.EnrichmentConfig()
|
||||
|
||||
result = sync_redmine_contacts.run_pipeline(
|
||||
redmine=redmine,
|
||||
crm=crm,
|
||||
enrichment_config=config,
|
||||
state=state,
|
||||
batch_size=100,
|
||||
dry_run=False,
|
||||
)
|
||||
|
||||
assert result["metrics"]["fetched"] == 1
|
||||
assert result["metrics"]["enriched"] == 1
|
||||
assert result["metrics"]["created"] == 1
|
||||
assert result["metrics"]["errors"] == 0
|
||||
assert state.last_sync_timestamp is not None
|
||||
|
||||
|
||||
@responses.activate
|
||||
def test_dry_run_does_not_write_crm():
|
||||
contacts = [
|
||||
{
|
||||
"id": 2,
|
||||
"first_name": "Ada",
|
||||
"last_name": "Lovelace",
|
||||
"email": "ada@example.com",
|
||||
"updated_at": "2026-07-04T12:00:00Z",
|
||||
}
|
||||
]
|
||||
responses.add(
|
||||
responses.GET,
|
||||
f"{BASE_URL}/contacts.json",
|
||||
json={"contacts": contacts, "total_count": 1},
|
||||
status=200,
|
||||
)
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
state_file = Path(tmpdir) / "state.json"
|
||||
redmine = RedmineClient(BASE_URL, "secret", project_id=1)
|
||||
crm = CrmClient()
|
||||
crm._store.clear()
|
||||
state = sync_redmine_contacts.FileSyncState(state_file)
|
||||
|
||||
result = sync_redmine_contacts.run_pipeline(
|
||||
redmine=redmine,
|
||||
crm=crm,
|
||||
state=state,
|
||||
batch_size=100,
|
||||
dry_run=True,
|
||||
)
|
||||
|
||||
assert result["dry_run"] is True
|
||||
assert result["metrics"]["created"] == 0
|
||||
assert not crm._store
|
||||
|
||||
|
||||
@responses.activate
|
||||
def test_pipeline_uses_last_sync_timestamp():
|
||||
contacts = [
|
||||
{
|
||||
"id": 3,
|
||||
"first_name": "Alan",
|
||||
"last_name": "Turing",
|
||||
"email": "alan@example.com",
|
||||
"updated_at": "2026-07-05T12:00:00Z",
|
||||
}
|
||||
]
|
||||
responses.add(
|
||||
responses.GET,
|
||||
f"{BASE_URL}/contacts.json",
|
||||
json={"contacts": contacts, "total_count": 1},
|
||||
status=200,
|
||||
)
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
state_file = Path(tmpdir) / "state.json"
|
||||
state_file.write_text(json.dumps({"last_sync_timestamp": "2026-07-04T00:00:00Z"}), encoding="utf-8")
|
||||
redmine = RedmineClient(BASE_URL, "secret", project_id=1)
|
||||
crm = CrmClient()
|
||||
crm._store.clear()
|
||||
state = sync_redmine_contacts.FileSyncState(state_file)
|
||||
|
||||
result = sync_redmine_contacts.run_pipeline(
|
||||
redmine=redmine,
|
||||
crm=crm,
|
||||
state=state,
|
||||
batch_size=100,
|
||||
dry_run=False,
|
||||
)
|
||||
|
||||
assert result["metrics"]["fetched"] == 1
|
||||
request = responses.calls[0].request
|
||||
assert "updated_on" in (request.url or "")
|
||||
|
||||
|
||||
def test_cli_requires_api_key():
|
||||
env = {"REDMINE_API_KEY": ""}
|
||||
with mock.patch.dict(os.environ, env, clear=True):
|
||||
with pytest.raises(RuntimeError, match="REDMINE_API_KEY"):
|
||||
sync_redmine_contacts._redmine_client_from_args(
|
||||
sync_redmine_contacts.build_argument_parser().parse_args(["--base-url", BASE_URL, "--dry-run"])
|
||||
)
|
||||
|
||||
|
||||
def test_file_sync_state_persists_and_reads():
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
state_file = Path(tmpdir) / "state.json"
|
||||
state = sync_redmine_contacts.FileSyncState(state_file)
|
||||
metrics = sync_redmine_contacts.SyncMetrics()
|
||||
metrics.fetched = 5
|
||||
state.record_run("2026-07-05T08:00:00Z", metrics)
|
||||
|
||||
new_state = sync_redmine_contacts.FileSyncState(state_file)
|
||||
assert new_state.last_sync_timestamp == "2026-07-05T08:00:00Z"
|
||||
assert new_state.last_metrics is not None
|
||||
assert new_state.last_metrics["fetched"] == 5
|
||||
|
||||
|
||||
def test_metrics_aggregate_counts():
|
||||
metrics = sync_redmine_contacts.SyncMetrics()
|
||||
metrics.fetched = 10
|
||||
metrics.enriched = 9
|
||||
metrics.skipped = 1
|
||||
metrics.created = 5
|
||||
metrics.updated = 3
|
||||
metrics.unchanged = 1
|
||||
metrics.errors = 0
|
||||
metrics.finished_at = sync_redmine_contacts._utc_now_iso()
|
||||
|
||||
d = metrics.to_dict()
|
||||
assert d["fetched"] == 10
|
||||
assert d["enriched"] == 9
|
||||
assert d["created"] + d["updated"] + d["unchanged"] == 9
|
||||
Reference in New Issue
Block a user