/ conftest.py
conftest.py
  1  import json
  2  import sys
  3  from pathlib import Path
  4  from typing import Any
  5  
  6  import pytest
  7  
  8  # Add rules/ to sys.path so that `from simple_types import ...` inside rules works
  9  # when pytest runs from the curator root directory.
 10  sys.path.insert(0, str(Path(__file__).parent / "rules"))
 11  
 12  
 13  @pytest.fixture
 14  def valid_nip35_event() -> dict[str, Any]:
 15      """Minimal valid NIP-35 event (kind 2003) with valid infohash and file tags."""
 16      return {
 17          "id": "a" * 64,  # Valid 64-char hex
 18          "pubkey": "b" * 64,  # Valid 64-char hex
 19          "created_at": 1234567890,
 20          "kind": 2003,
 21          "tags": [
 22              ["title", "Example Torrent"],
 23              ["x", "c" * 40],  # Valid 40-char hex infohash
 24              ["file", "example.mkv", "1073741824"],  # 1GB file
 25          ],
 26          "content": "Example torrent description",
 27          "sig": "d" * 128,  # Valid 128-char hex signature
 28      }
 29  
 30  
 31  @pytest.fixture
 32  def invalid_nip35_event_no_infohash(valid_nip35_event: dict[str, Any]) -> dict[str, Any]:
 33      """NIP-35 event missing the 'x' tag (infohash)."""
 34      event = valid_nip35_event.copy()
 35      event["tags"] = [tag for tag in event["tags"] if tag[0] != "x"]
 36      return event
 37  
 38  
 39  @pytest.fixture
 40  def invalid_nip35_event_bad_infohash(valid_nip35_event: dict[str, Any]) -> dict[str, Any]:
 41      """NIP-35 event with invalid infohash (too short)."""
 42      event = valid_nip35_event.copy()
 43      event["tags"] = [
 44          tag if tag[0] != "x" else ["x", "abc123"]
 45          for tag in event["tags"]
 46      ]
 47      return event
 48  
 49  
 50  @pytest.fixture
 51  def load_event_fixture():
 52      """Factory fixture to load events from JSON fixture files."""
 53      def _load(filename: str) -> list[dict[str, Any]]:
 54          fixture_path = Path(__file__).parent / "tests" / "fixtures" / filename
 55          with open(fixture_path) as f:
 56              return json.load(f)
 57      return _load
 58  
 59  
 60  @pytest.fixture
 61  def make_event(valid_nip35_event: dict[str, Any]):
 62      """Factory fixture to create events with custom overrides."""
 63      def _make(**kwargs) -> dict[str, Any]:
 64          event = valid_nip35_event.copy()
 65  
 66          # Handle tags specially - allow override or merge
 67          if "tags" in kwargs:
 68              event["tags"] = kwargs.pop("tags")
 69  
 70          # Override other fields
 71          event.update(kwargs)
 72          return event
 73  
 74      return _make
 75  
 76  
 77  @pytest.fixture
 78  def sample_torrent_event() -> dict[str, Any]:
 79      """Well-formed NIP-35 event with all required fields and multiple file tags."""
 80      return {
 81          "id": "0" * 64,
 82          "pubkey": "1" * 64,
 83          "created_at": 1234567890,
 84          "kind": 2003,
 85          "tags": [
 86              ["title", "Test Movie 1080p BluRay"],
 87              ["x", "abc123def456abc123def456abc123def4567890"],  # 40-char hex
 88              ["file", "Movie.mkv", "5368709120"],  # 5GB
 89              ["file", "Subs/English.srt", "52428"],  # 51KB
 90              ["tracker", "udp://tracker.example.com:6969/announce"],
 91              ["i", "tcat:movies"],
 92              ["t", "action"],
 93          ],
 94          "content": "A sample movie torrent for testing",
 95          "sig": "2" * 128,
 96      }
 97  
 98  
 99  @pytest.fixture
100  def sample_invalid_event() -> dict[str, Any]:
101      """Event missing required fields (no infohash 'x' tag)."""
102      return {
103          "id": "3" * 64,
104          "pubkey": "4" * 64,
105          "created_at": 1234567890,
106          "kind": 2003,
107          "tags": [
108              ["title", "Invalid Event"],
109              ["file", "test.txt", "1024"],
110          ],
111          "content": "Missing infohash",
112          "sig": "5" * 128,
113      }
114  
115  
116  @pytest.fixture
117  def sample_large_event() -> dict[str, Any]:
118      """Event with maximum-size fields (stress test)."""
119      return {
120          "id": "a" * 64,
121          "pubkey": "b" * 64,
122          "created_at": 1234567890,
123          "kind": 2003,
124          "tags": [
125              ["title", "X" * 500],  # Very long title
126              ["x", "c" * 40],
127              ["file", "a" * 255 + ".mkv", "1099511627776"],  # 255-char filename, 1TB file
128          ] + [["file", f"file{i}.mp4", str(1024 * 1024 * i)] for i in range(100)],  # 100 files
129          "content": "Y" * 10000,  # Very long content
130          "sig": "d" * 128,
131      }
132  
133  
134  @pytest.fixture
135  def mock_ruleset() -> dict[str, Any]:
136      """Minimal ruleset config for testing."""
137      return {
138          "name": "test-ruleset",
139          "version": "1.0.0",
140          "rules": [
141              {
142                  "id": "D-SCHEMA-03",
143                  "type": "deterministic",
144                  "enabled": True,
145                  "requirements": [],
146              },
147              {
148                  "id": "P-QUALITY-01",
149                  "type": "probabilistic",
150                  "enabled": True,
151                  "requirements": [],
152              },
153          ],
154      }
155  
156  
157  @pytest.fixture
158  def test_client():
159      """FastAPI TestClient fixture for integration tests."""
160      try:
161          from main import app
162          from fastapi.testclient import TestClient
163          return TestClient(app)
164      except Exception as e:
165          pytest.skip(f"Failed to import app: {e}")