68 lines
2.6 KiB
Python
68 lines
2.6 KiB
Python
|
|
from datetime import UTC, datetime
|
||
|
|
from decimal import Decimal
|
||
|
|
from pathlib import Path
|
||
|
|
|
||
|
|
from broker_sync.dedup import SyncRecordStore
|
||
|
|
from broker_sync.models import AccountType, Activity, ActivityType
|
||
|
|
|
||
|
|
|
||
|
|
def _buy(external_id: str) -> Activity:
|
||
|
|
return Activity(
|
||
|
|
external_id=external_id,
|
||
|
|
account_id="t212-isa",
|
||
|
|
account_type=AccountType.ISA,
|
||
|
|
date=datetime(2026, 1, 1, tzinfo=UTC),
|
||
|
|
activity_type=ActivityType.BUY,
|
||
|
|
symbol="VUAG",
|
||
|
|
quantity=Decimal("1"),
|
||
|
|
unit_price=Decimal("100"),
|
||
|
|
currency="GBP",
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
def test_store_schema_is_idempotent(tmp_path: Path) -> None:
|
||
|
|
db = tmp_path / "s.db"
|
||
|
|
SyncRecordStore(db) # creates schema
|
||
|
|
SyncRecordStore(db) # second open must not raise
|
||
|
|
assert db.exists()
|
||
|
|
|
||
|
|
|
||
|
|
def test_has_seen_returns_false_for_new(tmp_path: Path) -> None:
|
||
|
|
s = SyncRecordStore(tmp_path / "s.db")
|
||
|
|
assert s.has_seen("trading212", "t212-isa", "order-1") is False
|
||
|
|
|
||
|
|
|
||
|
|
def test_record_then_has_seen(tmp_path: Path) -> None:
|
||
|
|
s = SyncRecordStore(tmp_path / "s.db")
|
||
|
|
s.record("trading212", "t212-isa", "order-1", wealthfolio_activity_id="wf-42")
|
||
|
|
assert s.has_seen("trading212", "t212-isa", "order-1") is True
|
||
|
|
# Same (provider, account, external_id) from a different caller is still seen.
|
||
|
|
assert s.has_seen("trading212", "t212-isa", "order-1") is True
|
||
|
|
|
||
|
|
|
||
|
|
def test_record_is_idempotent(tmp_path: Path) -> None:
|
||
|
|
s = SyncRecordStore(tmp_path / "s.db")
|
||
|
|
s.record("trading212", "t212-isa", "order-1", wealthfolio_activity_id="wf-42")
|
||
|
|
s.record("trading212", "t212-isa", "order-1", wealthfolio_activity_id="wf-43")
|
||
|
|
# Second insert must not raise. Original first_seen / wealthfolio id preserved.
|
||
|
|
stored = s.get("trading212", "t212-isa", "order-1")
|
||
|
|
assert stored is not None
|
||
|
|
assert stored["wealthfolio_activity_id"] == "wf-42"
|
||
|
|
|
||
|
|
|
||
|
|
def test_scope_per_provider_and_account(tmp_path: Path) -> None:
|
||
|
|
s = SyncRecordStore(tmp_path / "s.db")
|
||
|
|
s.record("trading212", "t212-isa", "order-1", wealthfolio_activity_id="wf-1")
|
||
|
|
# Different provider, same external_id — NOT seen.
|
||
|
|
assert s.has_seen("invest-engine", "t212-isa", "order-1") is False
|
||
|
|
# Different account, same external_id — NOT seen.
|
||
|
|
assert s.has_seen("trading212", "t212-invest", "order-1") is False
|
||
|
|
|
||
|
|
|
||
|
|
def test_filter_new_drops_seen(tmp_path: Path) -> None:
|
||
|
|
s = SyncRecordStore(tmp_path / "s.db")
|
||
|
|
s.record("trading212", "t212-isa", "a", wealthfolio_activity_id=None)
|
||
|
|
activities = [_buy("a"), _buy("b"), _buy("c")]
|
||
|
|
fresh = s.filter_new("trading212", activities)
|
||
|
|
assert [a.external_id for a in fresh] == ["b", "c"]
|