Loading...
No commits yet
Not committed History
Blame
test_claim.py • 11.8 KB
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Timestamp: 2026-02-19
# File: tests/python/test_claim.py

"""Tests for scitex_writer claim feature.

Tests CRUD operations and LaTeX rendering via public API (scitex_writer.claim).
All file I/O uses tmp_path to avoid touching actual project files.
"""

import json
from pathlib import Path


def _make_project(tmp_path: Path) -> Path:
    """Create minimal project structure for claim tests."""
    shared = tmp_path / "00_shared"
    shared.mkdir(parents=True)
    return str(tmp_path)


class TestClaimAdd:
    """Test adding claims."""

    def test_add_statistic(self, tmp_path):
        import scitex_writer.claim as sc

        project_dir = _make_project(tmp_path)
        result = sc.add(
            project_dir=project_dir,
            claim_id="group_comparison",
            claim_type="statistic",
            value={"t": 4.23, "df": 34, "p": 0.00032, "d": 0.87},
            context="Group A vs B",
        )
        assert result["success"]
        assert result["claim_id"] == "group_comparison"

    def test_add_value(self, tmp_path):
        import scitex_writer.claim as sc

        project_dir = _make_project(tmp_path)
        result = sc.add(
            project_dir=project_dir,
            claim_id="rt_mean",
            claim_type="value",
            value={"value": 42.3, "unit": "ms"},
        )
        assert result["success"]

    def test_add_citation(self, tmp_path):
        import scitex_writer.claim as sc

        project_dir = _make_project(tmp_path)
        result = sc.add(
            project_dir=project_dir,
            claim_id="key_finding",
            claim_type="citation",
            value={"text": "as previously reported"},
        )
        assert result["success"]

    def test_add_creates_claims_json(self, tmp_path):
        import scitex_writer.claim as sc

        project_dir = _make_project(tmp_path)
        sc.add(
            project_dir=project_dir,
            claim_id="c1",
            claim_type="citation",
            value={"text": "test"},
        )
        claims_file = Path(project_dir) / "00_shared" / "claims.json"
        assert claims_file.exists()
        data = json.loads(claims_file.read_text())
        assert "c1" in data["claims"]

    def test_add_preserves_existing(self, tmp_path):
        import scitex_writer.claim as sc

        project_dir = _make_project(tmp_path)
        sc.add(
            project_dir=project_dir,
            claim_id="c1",
            claim_type="citation",
            value={"text": "first"},
        )
        sc.add(
            project_dir=project_dir,
            claim_id="c2",
            claim_type="citation",
            value={"text": "second"},
        )
        result = sc.list(project_dir=project_dir)
        assert result["count"] == 2


class TestClaimList:
    """Test listing claims."""

    def test_list_empty(self, tmp_path):
        import scitex_writer.claim as sc

        project_dir = _make_project(tmp_path)
        result = sc.list(project_dir=project_dir)
        assert result["success"]
        assert result["count"] == 0
        assert result["claims"] == []

    def test_list_after_add(self, tmp_path):
        import scitex_writer.claim as sc

        project_dir = _make_project(tmp_path)
        sc.add(
            project_dir=project_dir,
            claim_id="stat1",
            claim_type="statistic",
            value={"t": 2.1, "df": 20, "p": 0.05, "d": 0.4},
        )
        result = sc.list(project_dir=project_dir)
        assert result["success"]
        assert result["count"] == 1
        assert result["claims"][0]["claim_id"] == "stat1"
        assert result["claims"][0]["type"] == "statistic"

    def test_list_includes_preview(self, tmp_path):
        import scitex_writer.claim as sc

        project_dir = _make_project(tmp_path)
        sc.add(
            project_dir=project_dir,
            claim_id="s1",
            claim_type="statistic",
            value={"t": 3.5, "df": 10, "p": 0.006, "d": 0.9},
        )
        result = sc.list(project_dir=project_dir)
        claim = result["claims"][0]
        assert "preview_nature" in claim


class TestClaimGet:
    """Test getting individual claims."""

    def test_get_existing(self, tmp_path):
        import scitex_writer.claim as sc

        project_dir = _make_project(tmp_path)
        sc.add(
            project_dir=project_dir,
            claim_id="my_claim",
            claim_type="citation",
            value={"text": "test text"},
        )
        result = sc.get(project_dir=project_dir, claim_id="my_claim")
        assert result["success"]
        assert result["claim"]["type"] == "citation"

    def test_get_missing(self, tmp_path):
        import scitex_writer.claim as sc

        project_dir = _make_project(tmp_path)
        result = sc.get(project_dir=project_dir, claim_id="nonexistent")
        assert not result["success"]


class TestClaimRemove:
    """Test removing claims."""

    def test_remove_existing(self, tmp_path):
        import scitex_writer.claim as sc

        project_dir = _make_project(tmp_path)
        sc.add(
            project_dir=project_dir,
            claim_id="to_delete",
            claim_type="citation",
            value={"text": "delete me"},
        )
        result = sc.remove(project_dir=project_dir, claim_id="to_delete")
        assert result["success"]
        assert sc.list(project_dir=project_dir)["count"] == 0

    def test_remove_missing(self, tmp_path):
        import scitex_writer.claim as sc

        project_dir = _make_project(tmp_path)
        result = sc.remove(project_dir=project_dir, claim_id="ghost")
        assert not result["success"]


class TestClaimFormat:
    """Test formatting claims into rendered strings."""

    def test_format_statistic_nature(self, tmp_path):
        import scitex_writer.claim as sc

        project_dir = _make_project(tmp_path)
        sc.add(
            project_dir=project_dir,
            claim_id="s1",
            claim_type="statistic",
            value={"t": 4.23, "df": 34, "p": 0.00032, "d": 0.87},
        )
        result = sc.format(project_dir=project_dir, claim_id="s1", style="nature")
        assert result["success"]
        assert "t" in result["rendered"]
        assert "4.23" in result["rendered"]

    def test_format_statistic_apa(self, tmp_path):
        import scitex_writer.claim as sc

        project_dir = _make_project(tmp_path)
        sc.add(
            project_dir=project_dir,
            claim_id="s1",
            claim_type="statistic",
            value={"t": 4.23, "df": 34, "p": 0.00032, "d": 0.87},
        )
        result = sc.format(project_dir=project_dir, claim_id="s1", style="apa")
        assert result["success"]
        assert "Cohen" in result["rendered"]

    def test_format_statistic_plain(self, tmp_path):
        import scitex_writer.claim as sc

        project_dir = _make_project(tmp_path)
        sc.add(
            project_dir=project_dir,
            claim_id="s1",
            claim_type="statistic",
            value={"t": 4.23, "df": 34, "p": 0.00032, "d": 0.87},
        )
        result = sc.format(project_dir=project_dir, claim_id="s1", style="plain")
        assert result["success"]
        assert "$" not in result["rendered"]

    def test_format_p_small(self, tmp_path):
        """p < 0.001 should render as < 0.001, not 0.000."""
        import scitex_writer.claim as sc

        project_dir = _make_project(tmp_path)
        sc.add(
            project_dir=project_dir,
            claim_id="s1",
            claim_type="statistic",
            value={"t": 5.0, "df": 100, "p": 0.0005, "d": 1.0},
        )
        result = sc.format(project_dir=project_dir, claim_id="s1", style="nature")
        assert result["success"]
        assert "0.001" in result["rendered"]  # < 0.001

    def test_format_citation(self, tmp_path):
        import scitex_writer.claim as sc

        project_dir = _make_project(tmp_path)
        sc.add(
            project_dir=project_dir,
            claim_id="c1",
            claim_type="citation",
            value={"text": "as shown previously"},
        )
        result = sc.format(project_dir=project_dir, claim_id="c1", style="nature")
        assert result["success"]
        assert "as shown previously" in result["rendered"]

    def test_format_value(self, tmp_path):
        import scitex_writer.claim as sc

        project_dir = _make_project(tmp_path)
        sc.add(
            project_dir=project_dir,
            claim_id="v1",
            claim_type="value",
            value={"value": 42.3, "unit": "ms"},
        )
        result = sc.format(project_dir=project_dir, claim_id="v1", style="nature")
        assert result["success"]
        assert "42.3" in result["rendered"]
        assert "ms" in result["rendered"]


class TestClaimRender:
    """Test rendering all claims to claims_rendered.tex."""

    def test_render_creates_tex(self, tmp_path):
        import scitex_writer.claim as sc

        project_dir = _make_project(tmp_path)
        sc.add(
            project_dir=project_dir,
            claim_id="grp",
            claim_type="statistic",
            value={"t": 4.23, "df": 34, "p": 0.00032, "d": 0.87},
        )
        result = sc.render(project_dir=project_dir)
        assert result["success"]
        tex_path = Path(project_dir) / "00_shared" / "claims_rendered.tex"
        assert tex_path.exists()

    def test_render_tex_has_macro(self, tmp_path):
        import scitex_writer.claim as sc

        project_dir = _make_project(tmp_path)
        sc.add(
            project_dir=project_dir,
            claim_id="my_stat",
            claim_type="statistic",
            value={"t": 2.0, "df": 10, "p": 0.03, "d": 0.5},
        )
        sc.render(project_dir=project_dir)
        tex = (Path(project_dir) / "00_shared" / "claims_rendered.tex").read_text()
        assert "\\stxclaim" in tex
        assert "mystat" in tex  # sanitized ID (underscores removed)

    def test_render_all_styles(self, tmp_path):
        """Rendered .tex defines nature, apa, and plain macros for each claim."""
        import scitex_writer.claim as sc

        project_dir = _make_project(tmp_path)
        sc.add(
            project_dir=project_dir,
            claim_id="s1",
            claim_type="statistic",
            value={"t": 3.0, "df": 20, "p": 0.005, "d": 0.7},
        )
        sc.render(project_dir=project_dir)
        tex = (Path(project_dir) / "00_shared" / "claims_rendered.tex").read_text()
        assert "@nature" in tex
        assert "@apa" in tex
        assert "@plain" in tex

    def test_render_empty(self, tmp_path):
        """Rendering with no claims still creates a valid .tex file."""
        import scitex_writer.claim as sc

        project_dir = _make_project(tmp_path)
        result = sc.render(project_dir=project_dir)
        assert result["success"]
        assert result["claims_count"] == 0
        tex_path = Path(project_dir) / "00_shared" / "claims_rendered.tex"
        assert tex_path.exists()


class TestClaimPublicApi:
    """Test that scitex_writer.claim exposes the correct public interface."""

    def test_public_functions_exist(self):
        import scitex_writer.claim as sc

        for name in ["add", "list", "get", "remove", "format", "render"]:
            assert hasattr(sc, name), f"Missing: claim.{name}"
            assert callable(getattr(sc, name))

    def test_claim_not_in_top_level_all(self):
        """Internal dataclasses should not appear in scitex_writer.__all__."""
        import scitex_writer as sw

        for name in [
            "CompilationResult",
            "ManuscriptTree",
            "RevisionTree",
            "SupplementaryTree",
        ]:
            assert name not in sw.__all__, f"{name} should be hidden from __all__"

    def test_claim_in_top_level_all(self):
        """claim module should be in scitex_writer.__all__."""
        import scitex_writer as sw

        assert "claim" in sw.__all__