""" PIT (point-in-time) correctness + business logic tests for Form 4. Pure unit tests (no DB) for c-suite derivation, purchase_pct formula, and service query-building logic. """ import pytest from datetime import date, datetime, timezone from unittest.mock import AsyncMock, MagicMock, patch from app.services.insider_transaction_service import InsiderTransactionService # ------------------------------------------------------------------ # C-suite title derivation # ------------------------------------------------------------------ class TestDeriveTitleFlags: def test_ceo_full_title(self): flags = InsiderTransactionService._derive_title_flags("Chief Executive Officer") assert flags == {"is_ceo": True, "is_cfo": False, "is_c_suite": True} def test_ceo_abbrev(self): flags = InsiderTransactionService._derive_title_flags("CEO") assert flags["is_ceo"] is True assert flags["is_c_suite"] is True def test_cfo_full_title(self): flags = InsiderTransactionService._derive_title_flags("Chief Financial Officer") assert flags["is_cfo"] is True assert flags["is_c_suite"] is True def test_principal_financial_officer(self): flags = InsiderTransactionService._derive_title_flags("Principal Financial Officer") assert flags["is_cfo"] is True def test_president(self): flags = InsiderTransactionService._derive_title_flags("President, Americas Division") assert flags["is_c_suite"] is True assert flags["is_ceo"] is False def test_chairman(self): flags = InsiderTransactionService._derive_title_flags("Chairman of the Board") assert flags["is_c_suite"] is True def test_coo(self): flags = InsiderTransactionService._derive_title_flags("COO") assert flags["is_c_suite"] is True def test_evp_is_not_csuite(self): # "EVP Sales" does not match CEO/CFO/COO/... or "Chief X Officer" flags = InsiderTransactionService._derive_title_flags("EVP Sales") assert flags["is_c_suite"] is False def test_director_is_not_csuite(self): flags = InsiderTransactionService._derive_title_flags("Director") assert all(not v for v in flags.values()) def test_none_title(self): flags = InsiderTransactionService._derive_title_flags(None) assert flags == {"is_ceo": False, "is_cfo": False, "is_c_suite": False} def test_empty_string(self): flags = InsiderTransactionService._derive_title_flags("") assert all(not v for v in flags.values()) def test_case_insensitive(self): flags = InsiderTransactionService._derive_title_flags("ceo") assert flags["is_ceo"] is True def test_ceo_and_president(self): flags = InsiderTransactionService._derive_title_flags("President and CEO") assert flags["is_ceo"] is True assert flags["is_c_suite"] is True def test_chief_marketing_officer(self): flags = InsiderTransactionService._derive_title_flags("Chief Marketing Officer") assert flags["is_c_suite"] is True assert flags["is_ceo"] is False # ------------------------------------------------------------------ # purchase_pct_of_holding formula (tested via _parse_transaction_element mock) # ------------------------------------------------------------------ class TestPurchasePct: """Test the purchase_pct_of_holding calculation logic directly.""" def _compute_pct(self, code, shares, shares_after): """Mirrors the formula in _parse_transaction_element.""" if code in ("P", "A") and shares is not None and shares > 0 and shares_after and shares_after > 0: return abs(shares) / shares_after return None def test_open_market_buy(self): pct = self._compute_pct("P", 100.0, 1000.0) assert pct == pytest.approx(0.1, rel=1e-6) def test_award(self): pct = self._compute_pct("A", 500.0, 5000.0) assert pct == pytest.approx(0.1, rel=1e-6) def test_sell_is_none(self): assert self._compute_pct("S", -100.0, 900.0) is None def test_zero_shares_after(self): assert self._compute_pct("P", 100.0, 0.0) is None def test_none_shares_after(self): assert self._compute_pct("P", 100.0, None) is None def test_negative_buy_shares_is_none(self): # Negative shares on a "P" code shouldn't happen, but guard assert self._compute_pct("P", -100.0, 1000.0) is None def test_small_buy(self): pct = self._compute_pct("P", 1.0, 1_000_000.0) assert pct == pytest.approx(1e-6, rel=1e-4) # ------------------------------------------------------------------ # PIT filter logic (mocked DB) # ------------------------------------------------------------------ class TestGetForm4Pit: """Verify that get_form4_pit builds correct date conditions.""" @pytest.mark.asyncio async def test_as_of_is_used_as_upper_bound(self): svc = InsiderTransactionService() mock_db = AsyncMock() mock_result = MagicMock() mock_result.scalar.return_value = 0 mock_result.scalars.return_value.all.return_value = [] mock_db.execute = AsyncMock(return_value=mock_result) rows, total = await svc.get_form4_pit( mock_db, ticker="AAPL", as_of=date(2024, 1, 15) ) assert total == 0 assert rows == [] # Verify execute was called (conditions were built) assert mock_db.execute.called @pytest.mark.asyncio async def test_buy_only_adds_code_filter(self): svc = InsiderTransactionService() mock_db = AsyncMock() mock_result = MagicMock() mock_result.scalar.return_value = 0 mock_result.scalars.return_value.all.return_value = [] mock_db.execute = AsyncMock(return_value=mock_result) # Should not raise rows, total = await svc.get_form4_pit( mock_db, ticker="NVDA", as_of=date(2024, 3, 5), buy_only=True ) assert mock_db.execute.called # ------------------------------------------------------------------ # Aggregate query # ------------------------------------------------------------------ class TestGetForm4Aggregate: @pytest.mark.asyncio async def test_returns_dict_structure(self): svc = InsiderTransactionService() mock_db = AsyncMock() mock_row = MagicMock() mock_row.buy_count = 3 mock_row.buy_dollar_total = 150000.0 mock_row.cluster_size = 2 mock_row.csuite_count = 1 mock_row.avg_pct_of_holding = 0.05 mock_row.last_filing_date = datetime(2024, 1, 10, tzinfo=timezone.utc) mock_result = MagicMock() mock_result.one.return_value = mock_row mock_db.execute = AsyncMock(return_value=mock_result) agg = await svc.get_form4_aggregate( mock_db, ticker="AAPL", as_of=date(2024, 1, 15), window_days=30 ) assert agg["symbol"] == "AAPL" assert agg["buy_count"] == 3 assert agg["cluster_size"] == 2 assert agg["csuite_count"] == 1 assert "recency_days" in agg assert "buy_dollar_total" in agg