"""Tests for edinet_mcp._diff module."""
from __future__ import annotations
from unittest.mock import AsyncMock, MagicMock, patch
import pytest
from edinet_mcp._diff import (
LineItemDiff,
_calculate_change,
_calculate_change_rate,
_calculate_summary,
_compare_statement,
_extract_current_value,
diff_statements,
)
from edinet_mcp.models import StatementData
class TestDiffHelpers:
"""Tests for diff helper functions."""
def test_extract_current_value_success(self) -> None:
"""Test extracting current value from statement."""
stmt = MagicMock(spec=StatementData)
stmt.__getitem__ = MagicMock(return_value={"当期": 1000, "前期": 800})
result = _extract_current_value(stmt, "売上高")
assert result == 1000.0
def test_extract_current_value_missing(self) -> None:
"""Test extracting value when item is missing."""
stmt = MagicMock(spec=StatementData)
stmt.__getitem__ = MagicMock(side_effect=KeyError("not found"))
result = _extract_current_value(stmt, "存在しない科目")
assert result is None
def test_calculate_change(self) -> None:
"""Test calculating absolute change."""
assert _calculate_change(100, 150) == 50
assert _calculate_change(100, 80) == -20
assert _calculate_change(None, 100) is None
assert _calculate_change(100, None) is None
def test_calculate_change_rate(self) -> None:
"""Test calculating percentage change."""
assert _calculate_change_rate(100, 150) == "+50.00%"
assert _calculate_change_rate(100, 80) == "-20.00%"
assert _calculate_change_rate(100, 100) == "+0.00%"
assert _calculate_change_rate(None, 100) is None
assert _calculate_change_rate(0, 100) is None # Cannot divide by zero
def test_calculate_summary(self) -> None:
"""Test summary calculation."""
diffs = [
LineItemDiff(
statement="income_statement",
科目="売上高",
period1_value=100,
period2_value=150,
増減額=50,
増減率="+50.00%",
),
LineItemDiff(
statement="income_statement",
科目="営業利益",
period1_value=50,
period2_value=30,
増減額=-20,
増減率="-40.00%",
),
LineItemDiff(
statement="balance_sheet",
科目="総資産",
period1_value=1000,
period2_value=1000,
増減額=0,
増減率="+0.00%",
),
]
summary = _calculate_summary(diffs)
assert summary["total_items"] == 3
assert summary["increased"] == 1
assert summary["decreased"] == 1
assert summary["unchanged"] == 1
assert len(summary["top_increases"]) == 1
assert summary["top_increases"][0]["科目"] == "売上高"
assert len(summary["top_decreases"]) == 1
assert summary["top_decreases"][0]["科目"] == "営業利益"
class TestCompareStatement:
"""Tests for statement comparison."""
def test_compare_statement_basic(self) -> None:
"""Test comparing two statements."""
stmt1 = MagicMock(spec=StatementData)
stmt1.labels = ["売上高", "営業利益"]
stmt1_data = {"売上高": {"当期": 100}, "営業利益": {"当期": 50}}
stmt1.__getitem__ = MagicMock(side_effect=lambda k: stmt1_data[k])
stmt2 = MagicMock(spec=StatementData)
stmt2.labels = ["売上高", "営業利益"]
stmt2_data = {"売上高": {"当期": 150}, "営業利益": {"当期": 60}}
stmt2.__getitem__ = MagicMock(side_effect=lambda k: stmt2_data[k])
diffs = _compare_statement(stmt1, stmt2, "income_statement")
assert len(diffs) == 2
# Check sales diff
sales_diff = next(d for d in diffs if d["科目"] == "売上高")
assert sales_diff["period1_value"] == 100
assert sales_diff["period2_value"] == 150
assert sales_diff["増減額"] == 50
assert sales_diff["増減率"] == "+50.00%"
def test_compare_statement_with_new_items(self) -> None:
"""Test comparing when period2 has new items."""
stmt1 = MagicMock(spec=StatementData)
stmt1.labels = ["売上高"]
def stmt1_getitem(k):
if k == "売上高":
return {"当期": 100}
raise KeyError(k)
stmt1.__getitem__ = MagicMock(side_effect=stmt1_getitem)
stmt2 = MagicMock(spec=StatementData)
stmt2.labels = ["売上高", "新しい科目"]
def stmt2_getitem(k):
return {"売上高": {"当期": 150}, "新しい科目": {"当期": 50}}[k]
stmt2.__getitem__ = MagicMock(side_effect=stmt2_getitem)
diffs = _compare_statement(stmt1, stmt2, "income_statement")
assert len(diffs) == 2
new_item = next(d for d in diffs if d["科目"] == "新しい科目")
assert new_item["period1_value"] is None
assert new_item["period2_value"] == 50
@pytest.mark.asyncio
class TestDiffStatements:
"""Tests for diff_statements async function."""
async def test_diff_statements_success(self) -> None:
"""Test successful diff of two periods."""
from edinet_mcp.models import AccountingStandard
# Mock client
client = MagicMock()
# Create mock filing
mock_filing1 = MagicMock()
mock_filing1.company_name = "テスト会社"
mock_filing2 = MagicMock()
mock_filing2.company_name = "テスト会社"
# Create mock statements
stmt1 = MagicMock()
stmt1.filing = mock_filing1
stmt1.accounting_standard = AccountingStandard.JGAAP
stmt1.income_statement = MagicMock()
stmt1.income_statement.labels = ["売上高"]
stmt1.income_statement.__getitem__ = MagicMock(return_value={"当期": 100})
stmt1.balance_sheet = MagicMock()
stmt1.balance_sheet.labels = []
stmt1.cash_flow_statement = MagicMock()
stmt1.cash_flow_statement.labels = []
stmt2 = MagicMock()
stmt2.filing = mock_filing2
stmt2.accounting_standard = AccountingStandard.JGAAP
stmt2.income_statement = MagicMock()
stmt2.income_statement.labels = ["売上高"]
stmt2.income_statement.__getitem__ = MagicMock(return_value={"当期": 150})
stmt2.balance_sheet = MagicMock()
stmt2.balance_sheet.labels = []
stmt2.cash_flow_statement = MagicMock()
stmt2.cash_flow_statement.labels = []
# Mock get_financial_statements
with patch.object(client, "get_financial_statements", new_callable=AsyncMock) as mock_get:
mock_get.side_effect = [stmt1, stmt2]
result = await diff_statements(client, "E00001", "2023", "2024")
assert result["edinet_code"] == "E00001"
assert result["company_name"] == "テスト会社"
assert result["period1"] == "2023"
assert result["period2"] == "2024"
assert len(result["diffs"]) > 0
assert "summary" in result
# Verify both periods were fetched
assert mock_get.call_count == 2
async def test_diff_statements_fetch_error(self) -> None:
"""Test error handling when fetch fails."""
client = MagicMock()
with patch.object(client, "get_financial_statements", new_callable=AsyncMock) as mock_get:
mock_get.side_effect = ValueError("Statement not found")
with pytest.raises(ValueError, match="Failed to fetch"):
await diff_statements(client, "E00001", "2023", "2024")
class TestLineItemDiff:
"""Tests for LineItemDiff TypedDict."""
def test_line_item_diff_creation(self) -> None:
"""Test creating a LineItemDiff."""
diff = LineItemDiff(
statement="income_statement",
科目="売上高",
period1_value=100,
period2_value=150,
増減額=50,
増減率="+50.00%",
)
assert diff["statement"] == "income_statement"
assert diff["科目"] == "売上高"
assert diff["増減額"] == 50
assert diff["増減率"] == "+50.00%"