|
| 1 | +from typing import Any, Dict, List |
| 2 | + |
| 3 | +import pytest |
| 4 | + |
| 5 | +from apilinker.api_linker import ApiLinker |
| 6 | +from apilinker.core.connector import ApiConnector |
| 7 | + |
| 8 | + |
| 9 | +class DummyConnector(ApiConnector): |
| 10 | + def __init__(self): |
| 11 | + super().__init__("rest", base_url="https://x", endpoints={}) |
| 12 | + self.sent: List[Dict[str, Any]] = [] |
| 13 | + self.endpoints["src"] = type("Cfg", (), {"path": "/s", "method": "GET", "params": {}, "headers": {}, "body_template": None, "pagination": None, "response_path": None, "response_schema": None, "request_schema": None})() # type: ignore |
| 14 | + self.endpoints["dst"] = type("Cfg", (), {"path": "/t", "method": "POST", "params": {}, "headers": {}, "body_template": None, "pagination": None, "response_path": None, "response_schema": None, "request_schema": None})() # type: ignore |
| 15 | + |
| 16 | + def _prepare_request(self, endpoint_name, params=None): |
| 17 | + return {"method": "GET", "url": "/", "headers": {}, "params": {}, "json": None} |
| 18 | + |
| 19 | + def fetch_data(self, endpoint_name: str, params=None): |
| 20 | + # return duplicate items to test idempotency filtering |
| 21 | + return [{"id": 1}, {"id": 1}, {"id": 2}] |
| 22 | + |
| 23 | + def send_data(self, endpoint_name: str, data): |
| 24 | + if isinstance(data, list): |
| 25 | + self.sent.extend(data) |
| 26 | + return {"success": True} |
| 27 | + return {"success": True} |
| 28 | + |
| 29 | + |
| 30 | +def test_idempotency_filters_duplicates(monkeypatch): |
| 31 | + l = ApiLinker(log_level="ERROR") |
| 32 | + l.source = DummyConnector() |
| 33 | + l.target = DummyConnector() |
| 34 | + l.mapper.add_mapping("src", "dst", [{"source": "id", "target": "id"}]) |
| 35 | + l.idempotency_config = {"enabled": True, "salt": "s"} |
| 36 | + |
| 37 | + res = l.sync(source_endpoint="src", target_endpoint="dst") |
| 38 | + assert res.success |
| 39 | + # filtered to unique ids |
| 40 | + assert [x["id"] for x in l.target.sent] == [1, 2] |
| 41 | + |
| 42 | + |
| 43 | +def test_strict_schema_validation_failure(monkeypatch): |
| 44 | + l = ApiLinker(log_level="ERROR") |
| 45 | + src = DummyConnector() |
| 46 | + dst = DummyConnector() |
| 47 | + # define a strict request schema that will fail |
| 48 | + dst.endpoints["dst"].request_schema = {"type": "object", "properties": {"id": {"type": "string"}}, "required": ["id"]} |
| 49 | + l.source = src |
| 50 | + l.target = dst |
| 51 | + l.mapper.add_mapping("src", "dst", [{"source": "id", "target": "id"}]) |
| 52 | + l.validation_config = {"strict_mode": True} |
| 53 | + |
| 54 | + result = l.sync(source_endpoint="src", target_endpoint="dst") |
| 55 | + assert result.success is False |
| 56 | + assert result.errors, "expected validation errors recorded" |
| 57 | + |
| 58 | + |
| 59 | +def test_state_store_injection(tmp_path, monkeypatch): |
| 60 | + # configure state store in linker via direct attribute |
| 61 | + l = ApiLinker(log_level="ERROR") |
| 62 | + src = DummyConnector() |
| 63 | + dst = DummyConnector() |
| 64 | + l.source = src |
| 65 | + l.target = dst |
| 66 | + l.mapper.add_mapping("src", "dst", [{"source": "id", "target": "id"}]) |
| 67 | + |
| 68 | + from apilinker.core.state_store import FileStateStore |
| 69 | + |
| 70 | + st = FileStateStore(str(tmp_path / "s.json"), default_last_sync="2000-01-01T00:00:00+00:00") |
| 71 | + l.state_store = st |
| 72 | + |
| 73 | + # Sync with params None should inject updated_since |
| 74 | + res = l.sync(source_endpoint="src", target_endpoint="dst", params=None) |
| 75 | + assert res.success |
0 commit comments