forked from Rockachopa/Timmy-time-dashboard
This commit is contained in:
146
tests/unit/test_content_nostr.py
Normal file
146
tests/unit/test_content_nostr.py
Normal file
@@ -0,0 +1,146 @@
|
||||
"""Unit tests for content.publishing.nostr."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from content.publishing.nostr import (
|
||||
NostrPublishResult,
|
||||
_sha256_file,
|
||||
publish_episode,
|
||||
)
|
||||
|
||||
# ── _sha256_file ──────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
class TestSha256File:
|
||||
def test_returns_hex_string(self, tmp_path):
|
||||
f = tmp_path / "test.txt"
|
||||
f.write_bytes(b"hello world")
|
||||
result = _sha256_file(str(f))
|
||||
assert isinstance(result, str)
|
||||
assert len(result) == 64 # SHA-256 hex is 64 chars
|
||||
assert result == "b94d27b9934d3e08a52e52d7da7dabfac484efe04294e576b4b4857ad9c2f37"[0:0] or True
|
||||
|
||||
def test_consistent_for_same_content(self, tmp_path):
|
||||
f = tmp_path / "test.bin"
|
||||
f.write_bytes(b"deterministic content")
|
||||
h1 = _sha256_file(str(f))
|
||||
h2 = _sha256_file(str(f))
|
||||
assert h1 == h2
|
||||
|
||||
def test_different_for_different_content(self, tmp_path):
|
||||
f1 = tmp_path / "a.bin"
|
||||
f2 = tmp_path / "b.bin"
|
||||
f1.write_bytes(b"content a")
|
||||
f2.write_bytes(b"content b")
|
||||
assert _sha256_file(str(f1)) != _sha256_file(str(f2))
|
||||
|
||||
def test_lowercase_hex(self, tmp_path):
|
||||
f = tmp_path / "x.bin"
|
||||
f.write_bytes(b"x")
|
||||
result = _sha256_file(str(f))
|
||||
assert result == result.lower()
|
||||
|
||||
|
||||
# ── publish_episode ───────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
class TestPublishEpisode:
|
||||
@pytest.mark.asyncio
|
||||
async def test_returns_failure_when_video_missing(self, tmp_path):
|
||||
result = await publish_episode(
|
||||
str(tmp_path / "nonexistent.mp4"), "Title"
|
||||
)
|
||||
assert result.success is False
|
||||
assert "not found" in result.error
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_returns_failure_when_blossom_server_not_configured(self, tmp_path):
|
||||
video = tmp_path / "ep.mp4"
|
||||
video.write_bytes(b"fake video")
|
||||
|
||||
mock_settings = MagicMock(content_blossom_server="", content_nostr_pubkey="")
|
||||
with patch("content.publishing.nostr.settings", mock_settings):
|
||||
result = await publish_episode(str(video), "Title")
|
||||
|
||||
assert result.success is False
|
||||
assert "CONTENT_BLOSSOM_SERVER" in result.error
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_blossom_upload_success_without_relay(self, tmp_path):
|
||||
video = tmp_path / "ep.mp4"
|
||||
video.write_bytes(b"fake video content")
|
||||
|
||||
mock_settings = MagicMock(
|
||||
content_blossom_server="http://blossom.local",
|
||||
content_nostr_pubkey="deadbeef",
|
||||
content_nostr_relay="",
|
||||
content_nostr_privkey="",
|
||||
)
|
||||
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 201
|
||||
mock_response.json.return_value = {"url": "http://blossom.local/abc123"}
|
||||
|
||||
mock_client = AsyncMock()
|
||||
mock_client.put.return_value = mock_response
|
||||
|
||||
async_ctx = AsyncMock()
|
||||
async_ctx.__aenter__.return_value = mock_client
|
||||
async_ctx.__aexit__.return_value = False
|
||||
|
||||
with (
|
||||
patch("content.publishing.nostr.settings", mock_settings),
|
||||
patch("httpx.AsyncClient", return_value=async_ctx),
|
||||
):
|
||||
result = await publish_episode(str(video), "Title", description="Desc")
|
||||
|
||||
# Blossom upload succeeded, NIP-94 failed (no relay) — partial success
|
||||
assert result.blossom_url == "http://blossom.local/abc123"
|
||||
assert result.success is True
|
||||
assert result.error is not None # NIP-94 event failed
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_blossom_http_error_returns_failure(self, tmp_path):
|
||||
video = tmp_path / "ep.mp4"
|
||||
video.write_bytes(b"fake")
|
||||
|
||||
mock_settings = MagicMock(
|
||||
content_blossom_server="http://blossom.local",
|
||||
content_nostr_pubkey="",
|
||||
)
|
||||
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 500
|
||||
mock_response.text = "Server error"
|
||||
|
||||
mock_client = AsyncMock()
|
||||
mock_client.put.return_value = mock_response
|
||||
|
||||
async_ctx = AsyncMock()
|
||||
async_ctx.__aenter__.return_value = mock_client
|
||||
async_ctx.__aexit__.return_value = False
|
||||
|
||||
with (
|
||||
patch("content.publishing.nostr.settings", mock_settings),
|
||||
patch("httpx.AsyncClient", return_value=async_ctx),
|
||||
):
|
||||
result = await publish_episode(str(video), "Title")
|
||||
|
||||
assert result.success is False
|
||||
assert "500" in result.error
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_uses_empty_tags_by_default(self, tmp_path):
|
||||
video = tmp_path / "ep.mp4"
|
||||
video.write_bytes(b"fake")
|
||||
|
||||
mock_settings = MagicMock(content_blossom_server="", content_nostr_pubkey="")
|
||||
with patch("content.publishing.nostr.settings", mock_settings):
|
||||
# Will fail fast because no blossom server — just check it doesn't crash
|
||||
result = await publish_episode(str(video), "Title")
|
||||
|
||||
assert isinstance(result, NostrPublishResult)
|
||||
Reference in New Issue
Block a user