Merge pull request #444 from Yong-yuan-X/fix/centralize-test-path-setup

This commit is contained in:
Trevin Chow
2026-05-22 08:09:20 -07:00
committed by GitHub
84 changed files with 194 additions and 578 deletions
+4
View File
@@ -0,0 +1,4 @@
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "skills" / "last30days" / "scripts"))
-5
View File
@@ -5,11 +5,7 @@ comparisons, 'difference between X and Y' phrasing, trailing context
leaking into entities, degenerate inputs, and false-positive resistance.
"""
import sys
import unittest
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "skills" / "last30days" / "scripts"))
from lib import planner
@@ -193,6 +189,5 @@ class TestNoiseWordEntities(unittest.TestCase):
entities = planner._comparison_entities("Swift vs Rust vs Go")
self.assertTrue(any("Go" in e for e in entities))
if __name__ == "__main__":
unittest.main()
-6
View File
@@ -2,16 +2,12 @@ import json
import os
import shutil
import subprocess
import sys
import textwrap
import unittest
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "skills" / "last30days" / "scripts"))
from lib.bird_x import parse_bird_response
REPO_ROOT = Path(__file__).resolve().parents[1]
VENDORED_BIRD = REPO_ROOT / "skills" / "last30days" / "scripts" / "lib" / "vendor" / "bird-search" / "bird-search.mjs"
@@ -31,7 +27,6 @@ class TestBirdXEngagementZero(unittest.TestCase):
self.assertEqual(0, items[0]["engagement"]["likes"])
self.assertEqual(5, items[0]["engagement"]["reposts"])
@unittest.skipUnless(shutil.which("node"), "node is required for vendored Bird tests")
class TestVendoredBirdRuntime(unittest.TestCase):
def test_check_uses_env_credentials_without_browser_cookie_dependency(self):
@@ -305,6 +300,5 @@ class TestRunBirdSearchJsonDecodeRetry(unittest.TestCase):
self.assertEqual(response, timeout_error)
mock_sleep.assert_not_called()
if __name__ == "__main__":
unittest.main()
-4
View File
@@ -1,12 +1,9 @@
"""Tests for bluesky module."""
import os
import sys
import unittest
from pathlib import Path
from unittest.mock import patch, MagicMock
sys.path.insert(0, str(Path(__file__).parent.parent / "skills" / "last30days" / "scripts"))
from lib import bluesky
@@ -357,6 +354,5 @@ class TestAppPasswordFormat(unittest.TestCase):
# Defensive: don't crash on iterables
self.assertFalse(bluesky._validate_app_password_format(["wfwp", "cq7o", "5six", "7wy5"]))
if __name__ == "__main__":
unittest.main()
-4
View File
@@ -1,11 +1,8 @@
import sys
import tempfile
import unittest
from pathlib import Path
from unittest import mock
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "skills" / "last30days" / "scripts"))
import briefing
import store
@@ -52,6 +49,5 @@ class BriefingV3Tests(unittest.TestCase):
finally:
briefing.BRIEFS_DIR = old_briefs_dir
if __name__ == "__main__":
unittest.main()
-5
View File
@@ -7,11 +7,7 @@ where prompting techniques actually live.
"""
import re
import sys
import unittest
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "skills" / "last30days" / "scripts"))
from lib import categories
from lib.categories import CATEGORY_PEERS, detect_category, peer_subs_for
@@ -149,6 +145,5 @@ class CategoryMapInvariants(unittest.TestCase):
self.assertGreaterEqual(len(CATEGORY_PEERS), 8)
self.assertLessEqual(len(CATEGORY_PEERS), 20)
if __name__ == "__main__":
unittest.main()
-6
View File
@@ -13,17 +13,12 @@ Fixture reference: `tests/fixtures/prompting-gpt-image-2-resolved-block.md`.
"""
import io
import sys
import unittest
from contextlib import redirect_stderr
from pathlib import Path
from unittest.mock import patch
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "skills" / "last30days" / "scripts"))
from lib import resolve
OPENAI_BRAND_SUBREDDIT_RESULTS = [
{
"title": "r/OpenAI community hub",
@@ -140,6 +135,5 @@ class PromptingGptImage2RegressionGuard(unittest.TestCase):
self.assertIsNone(result["category"])
self.assertNotIn("Matched category=", buf.getvalue())
if __name__ == "__main__":
unittest.main()
+22 -27
View File
@@ -1,19 +1,15 @@
"""Tests for Chrome cookie extraction on macOS."""
import hashlib
import os
import sqlite3
import subprocess
import sys
import tempfile
from pathlib import Path
from unittest import mock
import pytest
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "skills" / "last30days"))
from scripts.lib.chrome_cookies import (
from lib.chrome_cookies import (
CHROME_COOKIES_DB,
CHROME_IV_HEX,
CHROME_KEY_LENGTH,
@@ -27,7 +23,6 @@ from scripts.lib.chrome_cookies import (
extract_chrome_cookies_macos,
)
# ---------------------------------------------------------------------------
# Helpers — create real encrypted cookie values using known key + system openssl
# ---------------------------------------------------------------------------
@@ -112,11 +107,11 @@ def _create_chrome_cookies_db(path: str, cookies: list[tuple], db_version: int =
conn.commit()
conn.close()
# ---------------------------------------------------------------------------
# PKCS7 padding tests
# ---------------------------------------------------------------------------
class TestPkcs7Padding:
def test_valid_padding_1(self):
# 1 byte of padding
@@ -143,11 +138,11 @@ class TestPkcs7Padding:
def test_empty_data(self):
assert _remove_pkcs7_padding(b"") is None
# ---------------------------------------------------------------------------
# Key derivation test
# ---------------------------------------------------------------------------
class TestKeyDerivation:
def test_derive_aes_key_deterministic(self):
key1 = _derive_aes_key(b"my_passphrase")
@@ -160,11 +155,11 @@ class TestKeyDerivation:
key2 = _derive_aes_key(b"passphrase_b")
assert key1 != key2
# ---------------------------------------------------------------------------
# Decryption test (real openssl, known key)
# ---------------------------------------------------------------------------
class TestDecryption:
def test_decrypt_v10_roundtrip(self):
"""Encrypt then decrypt — verifies the full pipeline works."""
@@ -197,28 +192,28 @@ class TestDecryption:
"""v10 prefix with no ciphertext should return None."""
assert _decrypt_v10_value(b"v10", KNOWN_AES_KEY, db_version=20) is None
# ---------------------------------------------------------------------------
# Chrome not installed → returns None
# ---------------------------------------------------------------------------
class TestChromeNotInstalled:
def test_db_not_found(self):
with mock.patch(
"scripts.lib.chrome_cookies.CHROME_COOKIES_DB",
"lib.chrome_cookies.CHROME_COOKIES_DB",
Path("/nonexistent/path/Cookies"),
):
result = extract_chrome_cookies_macos(".x.com", ["auth_token"])
assert result is None
# ---------------------------------------------------------------------------
# Keychain access denied → returns None
# ---------------------------------------------------------------------------
class TestKeychainDenied:
def test_security_command_fails(self):
with mock.patch("scripts.lib.chrome_cookies.subprocess.run") as mock_run:
with mock.patch("lib.chrome_cookies.subprocess.run") as mock_run:
mock_run.return_value = subprocess.CompletedProcess(
args=[], returncode=44, stdout="", stderr="security: SecKeychainSearchCopyNext: The specified item could not be found in the keychain."
)
@@ -226,27 +221,27 @@ class TestKeychainDenied:
assert result is None
def test_security_command_not_found(self):
with mock.patch("scripts.lib.chrome_cookies.subprocess.run", side_effect=FileNotFoundError):
with mock.patch("lib.chrome_cookies.subprocess.run", side_effect=FileNotFoundError):
result = _get_chrome_encryption_key()
assert result is None
# ---------------------------------------------------------------------------
# openssl not found → returns None
# ---------------------------------------------------------------------------
class TestOpensslNotFound:
def test_openssl_missing(self):
encrypted = _encrypt_value_v10("test", KNOWN_AES_KEY)
with mock.patch("scripts.lib.chrome_cookies.subprocess.run", side_effect=FileNotFoundError):
with mock.patch("lib.chrome_cookies.subprocess.run", side_effect=FileNotFoundError):
result = _decrypt_v10_value(encrypted, KNOWN_AES_KEY, db_version=20)
assert result is None
# ---------------------------------------------------------------------------
# Unencrypted cookie values → returned as-is
# ---------------------------------------------------------------------------
class TestUnencryptedCookies:
def test_plain_value_returned(self, tmp_path):
"""Unencrypted cookies (value column populated) returned without decryption."""
@@ -256,18 +251,18 @@ class TestUnencryptedCookies:
(".x.com", "ct0", "plain_ct0_value", b""),
])
with mock.patch("scripts.lib.chrome_cookies.CHROME_COOKIES_DB", Path(db_path)):
with mock.patch("lib.chrome_cookies.CHROME_COOKIES_DB", Path(db_path)):
# No keychain needed for unencrypted values
with mock.patch("scripts.lib.chrome_cookies._get_chrome_encryption_key", return_value=None):
with mock.patch("lib.chrome_cookies._get_chrome_encryption_key", return_value=None):
result = extract_chrome_cookies_macos(".x.com", ["auth_token", "ct0"])
assert result == {"auth_token": "plain_token_value", "ct0": "plain_ct0_value"}
# ---------------------------------------------------------------------------
# Full integration: mock DB with real v10 encryption, mock Keychain
# ---------------------------------------------------------------------------
class TestFullExtraction:
def test_encrypted_cookies_extracted(self, tmp_path):
"""End-to-end: create DB with real v10-encrypted values, extract them."""
@@ -284,9 +279,9 @@ class TestFullExtraction:
(".other.com", "other", "", b""), # unrelated cookie
])
with mock.patch("scripts.lib.chrome_cookies.CHROME_COOKIES_DB", Path(db_path)):
with mock.patch("lib.chrome_cookies.CHROME_COOKIES_DB", Path(db_path)):
with mock.patch(
"scripts.lib.chrome_cookies._get_chromium_encryption_key",
"lib.chrome_cookies._get_chromium_encryption_key",
return_value=KNOWN_PASSPHRASE,
):
result = extract_chrome_cookies_macos(".x.com", ["auth_token", "ct0"])
@@ -301,8 +296,8 @@ class TestFullExtraction:
(".other.com", "session", "val", b""),
])
with mock.patch("scripts.lib.chrome_cookies.CHROME_COOKIES_DB", Path(db_path)):
with mock.patch("scripts.lib.chrome_cookies._get_chrome_encryption_key", return_value=None):
with mock.patch("lib.chrome_cookies.CHROME_COOKIES_DB", Path(db_path)):
with mock.patch("lib.chrome_cookies._get_chrome_encryption_key", return_value=None):
result = extract_chrome_cookies_macos(".x.com", ["auth_token"])
assert result is None
@@ -317,9 +312,9 @@ class TestFullExtraction:
(".x.com", "auth_token", "", encrypted_auth),
], db_version=24)
with mock.patch("scripts.lib.chrome_cookies.CHROME_COOKIES_DB", Path(db_path)):
with mock.patch("lib.chrome_cookies.CHROME_COOKIES_DB", Path(db_path)):
with mock.patch(
"scripts.lib.chrome_cookies._get_chromium_encryption_key",
"lib.chrome_cookies._get_chromium_encryption_key",
return_value=KNOWN_PASSPHRASE,
):
result = extract_chrome_cookies_macos(".x.com", ["auth_token"])
@@ -327,11 +322,11 @@ class TestFullExtraction:
assert result is not None
assert result["auth_token"] == auth_val
# ---------------------------------------------------------------------------
# DB version detection
# ---------------------------------------------------------------------------
class TestDbVersion:
def test_reads_version_from_meta(self, tmp_path):
db_path = str(tmp_path / "test.db")
-7
View File
@@ -1,16 +1,10 @@
# ruff: noqa: E402
"""CLI parsing and validation for --competitors / --competitors-list."""
from __future__ import annotations
import io
import sys
import unittest
from contextlib import redirect_stderr
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(REPO_ROOT / "skills" / "last30days" / "scripts"))
import last30days as cli
@@ -132,6 +126,5 @@ class CompetitorsCliTests(unittest.TestCase):
cli.resolve_competitors_args(args)
self.assertEqual(cm.exception.code, 2)
if __name__ == "__main__":
unittest.main()
+2 -6
View File
@@ -1,4 +1,3 @@
# ruff: noqa: E402
import json
import io
import shutil
@@ -11,13 +10,11 @@ from contextlib import redirect_stderr, redirect_stdout
from pathlib import Path
from unittest import mock
REPO_ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(REPO_ROOT / "skills" / "last30days" / "scripts"))
import last30days as cli
from lib import schema
REPO_ROOT = Path(__file__).resolve().parents[1]
class CliV3Tests(unittest.TestCase):
def make_report(self) -> schema.Report:
@@ -305,6 +302,5 @@ class CliV3Tests(unittest.TestCase):
)
self.assertIn("[GitHub] Canonicalized repos:", stderr.getvalue())
if __name__ == "__main__":
unittest.main()
-5
View File
@@ -1,8 +1,4 @@
import sys
import unittest
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "skills" / "last30days" / "scripts"))
from lib import cluster, schema
@@ -196,6 +192,5 @@ class TestClusterUncertainty(unittest.TestCase):
result = cluster._cluster_uncertainty(candidates)
self.assertEqual("thin-evidence", result)
if __name__ == "__main__":
unittest.main()
-7
View File
@@ -1,20 +1,14 @@
# ruff: noqa: E402
"""Tests for scripts/lib/fanout.run_competitor_fanout."""
from __future__ import annotations
import io
import sys
import threading
import time
import unittest
from contextlib import redirect_stderr
from pathlib import Path
from unittest import mock
REPO_ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(REPO_ROOT / "skills" / "last30days" / "scripts"))
from lib import fanout
@@ -155,6 +149,5 @@ class FanoutOrchestratorTests(unittest.TestCase):
)
self.assertEqual([label for label, _ in results], ["OpenAI"])
if __name__ == "__main__":
unittest.main()
@@ -1,4 +1,3 @@
# ruff: noqa: E402
"""Regression tests: main-topic flags must not leak into competitor sub-runs.
Based on 2026-04-22 Kanye West --competitors receipt where Drake and
@@ -10,15 +9,10 @@ via closure capture, config mutation, or any other path.
from __future__ import annotations
import io
import sys
import unittest
from contextlib import redirect_stderr
from pathlib import Path
from unittest import mock
REPO_ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(REPO_ROOT / "skills" / "last30days" / "scripts"))
def _fake_report(topic: str):
class _R:
@@ -191,6 +185,5 @@ class SubRunIsolationTests(unittest.TestCase):
by_topic["Kendrick Lamar"]["config"].get("_auto_resolve_context", ""),
)
if __name__ == "__main__":
unittest.main()
-8
View File
@@ -1,18 +1,12 @@
# ruff: noqa: E402
"""Tests for scripts/lib/competitors.discover_competitors."""
from __future__ import annotations
import io
import sys
import unittest
from contextlib import redirect_stderr
from pathlib import Path
from unittest import mock
REPO_ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(REPO_ROOT / "skills" / "last30days" / "scripts"))
from lib import competitors
@@ -23,7 +17,6 @@ def _serp(items: list[tuple[str, str]]) -> list[dict]:
for title, snippet in items
]
OPENAI_SERP = _serp(
[
("OpenAI vs Anthropic vs xAI: which is better?", "xAI and Anthropic now compete directly with OpenAI."),
@@ -147,6 +140,5 @@ class CompetitorDiscoveryTests(unittest.TestCase):
results = self._run(OPENAI_SERP, "OpenAI", count=0)
self.assertEqual(results, [])
if __name__ == "__main__":
unittest.main()
-6
View File
@@ -1,20 +1,15 @@
# ruff: noqa: E402
"""Tests for --competitors-plan JSON parsing and per-entity kwargs threading."""
from __future__ import annotations
import io
import json
import sys
import tempfile
import unittest
from contextlib import redirect_stderr
from pathlib import Path
from unittest import mock
REPO_ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(REPO_ROOT / "skills" / "last30days" / "scripts"))
import last30days as cli
@@ -175,6 +170,5 @@ class SubrunKwargsForTests(unittest.TestCase):
kwargs = cli.subrun_kwargs_for("X", {}, resolved=resolved)
self.assertEqual(kwargs["_context"], "Resolved context")
if __name__ == "__main__":
unittest.main()
@@ -1,4 +1,3 @@
# ruff: noqa: E402
"""Integration tests for per-entity Step 0.55 resolution inside competitor fan-out."""
from __future__ import annotations
@@ -7,12 +6,8 @@ import io
import sys
import unittest
from contextlib import redirect_stderr
from pathlib import Path
from unittest import mock
REPO_ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(REPO_ROOT / "skills" / "last30days" / "scripts"))
def _fake_report(topic: str):
"""Minimal Report stand-in for runner return values."""
@@ -326,6 +321,5 @@ class PerEntityResolveTests(unittest.TestCase):
return [runner(c) for c in competitors]
if __name__ == "__main__":
unittest.main()
+19 -24
View File
@@ -2,24 +2,19 @@
import configparser
import sqlite3
import sys
import textwrap
from pathlib import Path
from typing import Dict, List, Optional, Tuple
from unittest.mock import patch
import pytest
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "skills" / "last30days"))
from scripts.lib.cookie_extract import (
from lib.cookie_extract import (
extract_cookies,
extract_firefox_cookies,
_find_default_profile,
_get_firefox_profiles_dir,
)
@pytest.fixture
def mock_firefox_env(tmp_path):
"""Create a mock Firefox profiles directory with cookies.sqlite.
@@ -102,7 +97,7 @@ class TestExtractFirefoxCookies:
profiles_dir = mock_firefox_env()
with patch(
"scripts.lib.cookie_extract._get_firefox_profiles_dir",
"lib.cookie_extract._get_firefox_profiles_dir",
return_value=profiles_dir,
):
result = extract_firefox_cookies(".x.com", ["auth_token", "ct0"])
@@ -142,7 +137,7 @@ class TestExtractFirefoxCookies:
)
with patch(
"scripts.lib.cookie_extract._get_firefox_profiles_dir",
"lib.cookie_extract._get_firefox_profiles_dir",
return_value=profiles_dir,
):
result = extract_firefox_cookies(".x.com", ["auth_token", "ct0"])
@@ -154,10 +149,10 @@ class TestExtractFirefoxCookies:
def test_firefox_not_installed(self):
"""Returns None when Firefox profiles directory doesn't exist."""
with patch(
"scripts.lib.cookie_extract._get_firefox_profiles_dir",
"lib.cookie_extract._get_firefox_profiles_dir",
return_value=None,
), patch(
"scripts.lib.cookie_extract._is_wsl",
"lib.cookie_extract._is_wsl",
return_value=False,
):
result = extract_firefox_cookies(".x.com", ["auth_token"])
@@ -171,10 +166,10 @@ class TestExtractFirefoxCookies:
)
with patch(
"scripts.lib.cookie_extract._get_firefox_profiles_dir",
"lib.cookie_extract._get_firefox_profiles_dir",
return_value=profiles_dir,
), patch(
"scripts.lib.cookie_extract._is_wsl",
"lib.cookie_extract._is_wsl",
return_value=False,
):
result = extract_firefox_cookies(".x.com", ["auth_token", "ct0"])
@@ -192,10 +187,10 @@ class TestExtractFirefoxCookies:
)
with patch(
"scripts.lib.cookie_extract._get_firefox_profiles_dir",
"lib.cookie_extract._get_firefox_profiles_dir",
return_value=profiles_dir,
), patch(
"scripts.lib.cookie_extract._is_wsl",
"lib.cookie_extract._is_wsl",
return_value=False,
):
result = extract_firefox_cookies(".x.com", ["auth_token", "ct0"])
@@ -214,7 +209,7 @@ class TestExtractFirefoxCookies:
)
with patch(
"scripts.lib.cookie_extract._get_firefox_profiles_dir",
"lib.cookie_extract._get_firefox_profiles_dir",
return_value=profiles_dir,
):
result = extract_firefox_cookies(".x.com", ["auth_token"])
@@ -231,17 +226,17 @@ class TestExtractCookiesAuto:
profiles_dir = mock_firefox_env()
with (
patch("scripts.lib.cookie_extract.platform.system", return_value="Darwin"),
patch("lib.cookie_extract.platform.system", return_value="Darwin"),
patch(
"scripts.lib.cookie_extract.extract_chrome_cookies",
"lib.cookie_extract.extract_chrome_cookies",
return_value=None,
),
patch(
"scripts.lib.cookie_extract.extract_safari_cookies",
"lib.cookie_extract.extract_safari_cookies",
return_value=None,
),
patch(
"scripts.lib.cookie_extract._get_firefox_profiles_dir",
"lib.cookie_extract._get_firefox_profiles_dir",
return_value=profiles_dir,
),
):
@@ -257,9 +252,9 @@ class TestExtractCookiesAuto:
profiles_dir = mock_firefox_env()
with (
patch("scripts.lib.cookie_extract.platform.system", return_value="Linux"),
patch("lib.cookie_extract.platform.system", return_value="Linux"),
patch(
"scripts.lib.cookie_extract._get_firefox_profiles_dir",
"lib.cookie_extract._get_firefox_profiles_dir",
return_value=profiles_dir,
),
):
@@ -273,7 +268,7 @@ class TestExtractCookiesAuto:
profiles_dir = mock_firefox_env()
with patch(
"scripts.lib.cookie_extract._get_firefox_profiles_dir",
"lib.cookie_extract._get_firefox_profiles_dir",
return_value=profiles_dir,
):
result = extract_cookies("firefox", ".x.com", ["auth_token"])
@@ -289,7 +284,7 @@ class TestExtractCookiesAuto:
def test_chrome_delegates_to_chrome_module(self):
"""Chrome extraction delegates to chrome_cookies module."""
with patch(
"scripts.lib.cookie_extract.extract_chrome_cookies",
"lib.cookie_extract.extract_chrome_cookies",
return_value={"auth_token": "chrome_tok"},
):
result = extract_cookies("chrome", ".x.com", ["auth_token"])
@@ -298,7 +293,7 @@ class TestExtractCookiesAuto:
def test_safari_delegates_to_safari_module(self):
"""Safari extraction delegates to safari_cookies module."""
with patch(
"scripts.lib.cookie_extract.extract_safari_cookies",
"lib.cookie_extract.extract_safari_cookies",
return_value={"auth_token": "safari_tok"},
):
result = extract_cookies("safari", ".x.com", ["auth_token"])
-4
View File
@@ -1,12 +1,9 @@
"""Tests for dates module."""
import sys
import unittest
from datetime import datetime, timedelta, timezone
from pathlib import Path
# Add lib to path
sys.path.insert(0, str(Path(__file__).parent.parent / "skills" / "last30days" / "scripts"))
from lib import dates
@@ -109,6 +106,5 @@ class TestRecencyScore(unittest.TestCase):
result = dates.recency_score(None)
self.assertEqual(result, 0)
if __name__ == "__main__":
unittest.main()
-5
View File
@@ -1,9 +1,5 @@
import sys
import unittest
from datetime import datetime, timedelta, timezone
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "skills" / "last30days" / "scripts"))
from lib import dates
@@ -58,6 +54,5 @@ class DatesV3Tests(unittest.TestCase):
self.assertEqual(100, dates.recency_score(future))
self.assertEqual(0, dates.recency_score(None))
if __name__ == "__main__":
unittest.main()
+7 -12
View File
@@ -1,10 +1,6 @@
"""Unit tests for dedupe.py: text normalization, similarity metrics, and deduplication."""
import sys
import unittest
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "skills" / "last30days" / "scripts"))
from lib import dedupe
from lib.schema import SourceItem
@@ -16,11 +12,11 @@ def _item(title: str, body: str = "", source: str = "reddit", item_id: str = "t1
url="https://example.com", engagement={}, metadata={},
)
# ---------------------------------------------------------------------------
# normalize_text
# ---------------------------------------------------------------------------
class TestNormalizeText(unittest.TestCase):
def test_lowercases(self):
@@ -35,11 +31,11 @@ class TestNormalizeText(unittest.TestCase):
def test_empty_string(self):
self.assertEqual(dedupe.normalize_text(""), "")
# ---------------------------------------------------------------------------
# get_ngrams
# ---------------------------------------------------------------------------
class TestGetNgrams(unittest.TestCase):
def test_simple_trigrams(self):
@@ -58,11 +54,11 @@ class TestGetNgrams(unittest.TestCase):
ngrams = dedupe.get_ngrams("A!B")
self.assertEqual(ngrams, {"a b"})
# ---------------------------------------------------------------------------
# jaccard_similarity
# ---------------------------------------------------------------------------
class TestJaccardSimilarity(unittest.TestCase):
def test_identical_sets(self):
@@ -81,11 +77,11 @@ class TestJaccardSimilarity(unittest.TestCase):
def test_both_empty(self):
self.assertAlmostEqual(dedupe.jaccard_similarity(set(), set()), 0.0)
# ---------------------------------------------------------------------------
# token_jaccard
# ---------------------------------------------------------------------------
class TestTokenJaccard(unittest.TestCase):
def test_identical_texts(self):
@@ -105,11 +101,11 @@ class TestTokenJaccard(unittest.TestCase):
# "am" is len 2, "great"/"terrible" are content
self.assertGreater(result, 0.0)
# ---------------------------------------------------------------------------
# hybrid_similarity
# ---------------------------------------------------------------------------
class TestHybridSimilarity(unittest.TestCase):
def test_identical_texts(self):
@@ -131,11 +127,11 @@ class TestHybridSimilarity(unittest.TestCase):
max(ngram_sim, token_sim),
)
# ---------------------------------------------------------------------------
# item_text
# ---------------------------------------------------------------------------
class TestItemText(unittest.TestCase):
def test_combines_fields(self):
@@ -159,11 +155,11 @@ class TestItemText(unittest.TestCase):
self.assertIn("john", text)
self.assertIn("r/python", text)
# ---------------------------------------------------------------------------
# dedupe_items
# ---------------------------------------------------------------------------
class TestDedupeItems(unittest.TestCase):
def test_keeps_unique_items(self):
@@ -212,6 +208,5 @@ class TestDedupeItems(unittest.TestCase):
result_loose = dedupe.dedupe_items(items, threshold=0.3)
self.assertEqual(len(result_loose), 1)
if __name__ == "__main__":
unittest.main()
+11 -16
View File
@@ -5,21 +5,17 @@ from __future__ import annotations
import json
import os
import shutil
import sys
from datetime import datetime, timedelta, timezone
from pathlib import Path
from unittest.mock import MagicMock, patch
import pytest
sys.path.insert(0, str(Path(__file__).parent.parent / "skills" / "last30days" / "scripts"))
from lib import digg # noqa: E402
from lib import subproc # noqa: E402
from lib import digg
from lib import subproc
# === Helpers ===
def _cluster(
cluster_url_id: str = "abc123xy",
title: str = "Sample cluster",
@@ -66,9 +62,9 @@ def _post(
def _stdout_for(payload: dict) -> subproc.SubprocResult:
return subproc.SubprocResult(returncode=0, stdout=json.dumps(payload), stderr="")
# === _parse_first_post_age ===
def test_parse_first_post_age_days():
today = datetime(2026, 5, 9, tzinfo=timezone.utc)
assert digg._parse_first_post_age("5d", today=today) == "2026-05-04"
@@ -104,9 +100,9 @@ def test_parse_first_post_age_invalid():
assert digg._parse_first_post_age("d") is None
assert digg._parse_first_post_age("-3d") is None
# === parse_digg_response ===
def test_parse_response_happy_path():
response = {
"results": [
@@ -193,9 +189,9 @@ def test_parse_response_engagement_rank_score():
assert by_id["top"]["engagement"]["rank_score"] == 50.0
assert by_id["off-leaderboard"]["engagement"]["rank_score"] == 0.0
# === _parse_post ===
def test_parse_post_happy():
out = digg._parse_post(_post(username="adam", body="Hello world"))
assert out is not None
@@ -210,9 +206,9 @@ def test_parse_post_drops_missing_body_or_handle_or_url():
assert digg._parse_post({"author": {"username": "x"}, "body": "txt", "xUrl": ""}) is None
assert digg._parse_post(None) is None # type: ignore[arg-type]
# === _run_cli / search_digg with stubbed subprocess ===
def test_search_digg_binary_missing_returns_empty(monkeypatch):
monkeypatch.setattr(digg.shutil, "which", lambda _: None)
out = digg.search_digg("anything", "2026-04-09", "2026-05-09")
@@ -280,9 +276,9 @@ def test_search_digg_empty_query_short_circuits(monkeypatch):
assert out["results"] == []
called.assert_not_called()
# === enrich_with_top_posts ===
def test_enrich_with_top_posts_attaches_posts(monkeypatch):
monkeypatch.setattr(digg.shutil, "which", lambda _: "/fake/path")
@@ -357,9 +353,9 @@ def test_enrich_top_k_zero_skips_all(monkeypatch):
digg.enrich_with_top_posts(items, top_k=0)
fake.assert_not_called()
# === enrich_source_items (post-dedupe path) ===
class _FakeSourceItem:
def __init__(self, source, item_id, engagement, metadata):
self.source = source
@@ -410,14 +406,14 @@ def test_enrich_source_items_falls_back_to_item_id(monkeypatch):
digg.enrich_source_items(items, top_k=1)
assert captured["cluster_id"] == "fallbackid"
# === Live tests (opt-in) ===
LIVE = os.environ.get("LAST30DAYS_DIGG_LIVE", "").lower() in ("1", "true", "yes")
HAVE_BIN = shutil.which(digg.CLI_BIN) is not None
@pytest.mark.skipif(not (LIVE and HAVE_BIN), reason="LAST30DAYS_DIGG_LIVE not set or digg-pp-cli missing")
class TestLiveDigg:
def test_search_returns_clusters(self):
out = digg.search_digg("claude code", "2026-04-09", "2026-05-09", depth="quick")
@@ -451,6 +447,5 @@ class TestLiveDigg:
posts = digg.fetch_top_posts("notarealclusterid", posts_per=2)
assert posts == []
if __name__ == "__main__":
pytest.main([__file__, "-v"])
-4
View File
@@ -1,11 +1,8 @@
"""Tests for entity_extract module."""
import sys
import unittest
from pathlib import Path
# Add lib to path
sys.path.insert(0, str(Path(__file__).parent.parent / "skills" / "last30days" / "scripts"))
from lib import entity_extract
@@ -162,6 +159,5 @@ class TestExtractEntities(unittest.TestCase):
result = entity_extract.extract_entities([], [])
self.assertSetEqual(set(result.keys()), {"x_handles", "x_hashtags", "reddit_subreddits"})
if __name__ == "__main__":
unittest.main()
-5
View File
@@ -1,8 +1,4 @@
import sys
import unittest
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "skills" / "last30days" / "scripts"))
from lib import entity_extract
@@ -53,6 +49,5 @@ class TestExtractSubreddits(unittest.TestCase):
def test_empty_items(self):
self.assertEqual([], entity_extract._extract_subreddits([]))
if __name__ == "__main__":
unittest.main()
-4
View File
@@ -1,14 +1,10 @@
"""Tests for browser cookie extraction integration in env.py."""
import os
import sys
from pathlib import Path
from unittest.mock import patch
import pytest
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "skills" / "last30days" / "scripts"))
from lib.env import extract_browser_credentials, COOKIE_DOMAINS
+1 -6
View File
@@ -1,9 +1,4 @@
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "skills" / "last30days"))
from scripts.lib import env
from lib import env
def test_include_sources_defaults_to_empty_string(monkeypatch, tmp_path):
+1 -8
View File
@@ -12,19 +12,15 @@ from __future__ import annotations
import re
import subprocess
import sys
from pathlib import Path
from unittest import mock
import pytest
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "skills" / "last30days" / "scripts"))
from lib import env # noqa: E402
from lib import env
SETUP_KEYCHAIN_SH = Path(__file__).resolve().parents[1] / "skills" / "last30days" / "scripts" / "setup-keychain.sh"
# ---------------------------------------------------------------------------
# _load_keychain unit tests
# ---------------------------------------------------------------------------
@@ -93,12 +89,10 @@ def test_load_keychain_skips_empty_stdout():
mock.patch("subprocess.run", return_value=_run_result(0, "")):
assert env._load_keychain(["XAI_API_KEY"]) == {}
# ---------------------------------------------------------------------------
# get_config integration tests
# ---------------------------------------------------------------------------
@pytest.fixture
def clean_env(monkeypatch, tmp_path):
"""Hide every key get_config might touch and point CONFIG_FILE at a
@@ -154,7 +148,6 @@ def test_get_config_openai_key_can_come_from_keychain(clean_env):
assert cfg["OPENAI_API_KEY"] == "sk-from-kc"
assert cfg["OPENAI_AUTH_SOURCE"] == "api_key"
# ---------------------------------------------------------------------------
# Drift guard: lib/env.py KEYCHAIN_KEYS and setup-keychain.sh ALL_KEYS must
# stay in lockstep. A mismatch means users storing a key via the helper script
-4
View File
@@ -1,11 +1,8 @@
import os
import sys
import unittest
from pathlib import Path
from unittest import mock
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "skills" / "last30days" / "scripts"))
from lib import bird_x, env
@@ -69,6 +66,5 @@ class ThreadsAvailabilityTests(unittest.TestCase):
"INCLUDE_SOURCES": "",
}))
if __name__ == "__main__":
unittest.main()
-4
View File
@@ -1,13 +1,10 @@
import json
import os
import sys
import tempfile
import unittest
from pathlib import Path
from unittest import mock
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "skills" / "last30days" / "scripts"))
import evaluate_search_quality as evaluator
@@ -202,6 +199,5 @@ class EvaluatorV3Tests(unittest.TestCase):
self.assertIn("| topic a | 0.10 | 0.30 |", summary)
self.assertEqual("HEAD~1", metrics["baseline"])
if __name__ == "__main__":
unittest.main()
-3
View File
@@ -1,4 +1,3 @@
# ruff: noqa: E402
"""Tests for the BRAVE/SERPER web-promo suppression when hosting-model-driven."""
from __future__ import annotations
@@ -11,7 +10,6 @@ import unittest
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(REPO_ROOT / "skills" / "last30days" / "scripts"))
def _engine() -> Path:
@@ -90,6 +88,5 @@ class FooterNudgeSuppressionTests(unittest.TestCase):
msg="web promo should be suppressed when --plan is passed",
)
if __name__ == "__main__":
unittest.main()
-7
View File
@@ -1,8 +1,4 @@
import sys
import unittest
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "skills" / "last30days" / "scripts"))
from lib import fusion, schema
@@ -52,7 +48,6 @@ class FusionV3Tests(unittest.TestCase):
self.assertEqual({"reddit", "x"}, set(merged.sources))
self.assertEqual(2, len(merged.source_items))
def test_diversify_pool_guarantees_min_per_qualifying_source(self):
"""Every qualifying source (local_relevance >= 0.25) gets at least 2
items in the fused pool.
@@ -118,7 +113,6 @@ class FusionV3Tests(unittest.TestCase):
f"Source '{src}' has {source_counts.get(src, 0)} items, expected >= 2",
)
def test_diversify_pool_denies_slots_for_low_relevance_source(self):
"""Sources with best local_relevance < 0.25 do not get reserved slots.
@@ -439,6 +433,5 @@ class TestUrlNormalization(unittest.TestCase):
_normalize_url("https://reddit.com/r/test"),
)
if __name__ == "__main__":
unittest.main()
-4
View File
@@ -1,12 +1,9 @@
"""Tests for GitHub source module."""
import json
import sys
import unittest
from pathlib import Path
from unittest.mock import patch, MagicMock
sys.path.insert(0, str(Path(__file__).parent.parent / "skills" / "last30days" / "scripts"))
from lib import github
@@ -158,6 +155,5 @@ class TestComputeRelevance(unittest.TestCase):
low = github._compute_relevance("react", "React", 20, 0, 0)
self.assertGreater(high, low)
if __name__ == "__main__":
unittest.main()
-5
View File
@@ -1,10 +1,6 @@
import sys
import unittest
from pathlib import Path
from unittest.mock import patch
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "skills" / "last30days" / "scripts"))
from lib import grounding
@@ -338,6 +334,5 @@ class RedditEnrichItemsTests(unittest.TestCase):
msg=f"Expected a rate-limit stderr message, got: {captured_stderr!r}",
)
if __name__ == "__main__":
unittest.main()
+17 -17
View File
@@ -1,20 +1,16 @@
"""Tests for hackernews.py - HN search via Algolia API."""
import json
import sys
from datetime import datetime, timezone
from pathlib import Path
from unittest.mock import Mock, patch
import pytest
sys.path.insert(0, str(Path(__file__).parent.parent / "skills" / "last30days" / "scripts"))
from lib import hackernews
# === Helper Functions ===
def create_mock_hit(
object_id="12345",
title="Test HN Story",
@@ -40,9 +36,9 @@ def create_mock_hit(
"url": url,
}
# === Tests for _date_to_unix() ===
def test_date_to_unix_basic():
"""Test converting YYYY-MM-DD to Unix timestamp."""
result = hackernews._date_to_unix("2026-01-01")
@@ -59,9 +55,9 @@ def test_date_to_unix_leap_day():
expected = datetime(2024, 2, 29, tzinfo=timezone.utc).timestamp()
assert result == int(expected)
# === Tests for _unix_to_date() ===
def test_unix_to_date_basic():
"""Test converting Unix timestamp to YYYY-MM-DD."""
ts = int(datetime(2026, 1, 15, tzinfo=timezone.utc).timestamp())
@@ -77,9 +73,9 @@ def test_unix_to_date_with_time():
assert result == "2026-01-15"
# === Tests for _strip_html() ===
def test_strip_html_basic():
"""Test HTML stripping and entity decoding."""
html_text = "<p>Hello &amp; goodbye</p>"
@@ -113,9 +109,9 @@ def test_strip_html_entities():
# Entities are decoded
assert "&" in result or "test" in result
# === Tests for _title_matches_query() ===
def test_title_matches_query_basic():
"""Test basic query matching."""
title = "New AI framework for developers"
@@ -199,10 +195,11 @@ def test_title_matches_query_flattens_hyphens_and_commas():
# query 'rust, go, zig' flattens; title contains 'go'
assert hackernews._title_matches_query("Go 1.24 generics update", "rust, go, zig") is True
# === Tests for search_hackernews() ===
@patch('lib.hackernews.http.request')
def test_search_hackernews_basic(mock_request):
"""Test basic HN search."""
mock_request.return_value = {
@@ -221,8 +218,9 @@ def test_search_hackernews_basic(mock_request):
assert len(result["hits"]) == 1
assert mock_request.called
@patch('lib.hackernews.http.request')
def test_search_hackernews_depth_config(mock_request):
"""Test that depth parameter controls hit count."""
mock_request.return_value = {"hits": [], "nbHits": 0}
@@ -235,8 +233,9 @@ def test_search_hackernews_depth_config(mock_request):
assert "hitsPerPage=15" in url
@patch('lib.hackernews.http.request')
def test_search_hackernews_date_filtering(mock_request):
"""Test that date range is applied correctly."""
mock_request.return_value = {"hits": [], "nbHits": 0}
@@ -250,8 +249,9 @@ def test_search_hackernews_date_filtering(mock_request):
assert "numericFilters" in url
assert "created_at_i" in url
@patch('lib.hackernews.http.request')
def test_search_hackernews_http_error_handling(mock_request):
"""Test graceful handling of HTTP errors."""
from lib.http import HTTPError
@@ -263,8 +263,9 @@ def test_search_hackernews_http_error_handling(mock_request):
assert result["hits"] == []
assert "error" in result
@patch('lib.hackernews.http.request')
def test_search_hackernews_engagement_filter(mock_request):
"""Test that low-engagement stories are filtered."""
mock_request.return_value = {"hits": [], "nbHits": 0}
@@ -277,9 +278,9 @@ def test_search_hackernews_engagement_filter(mock_request):
# Should filter for points > 2 (URL-encoded)
assert "points" in url and "%3E2" in url
# === Tests for parse_hackernews_response() ===
def test_parse_hackernews_response_basic():
"""Test parsing basic Algolia response."""
response = {
@@ -402,9 +403,9 @@ def test_parse_hackernews_response_empty_response():
assert items == []
# === Tests for engagement scoring ===
def test_engagement_score_calculation():
"""Test that engagement dict contains points and comments."""
response = {
@@ -435,6 +436,5 @@ def test_engagement_score_zero_values():
assert engagement["points"] == 0
assert engagement["comments"] == 0
if __name__ == "__main__":
pytest.main([__file__, "-v"])
-6
View File
@@ -1,17 +1,12 @@
# ruff: noqa: E402
"""Tests for the HTML emit renderer."""
from __future__ import annotations
import sys
import tempfile
import unittest
from html.parser import HTMLParser
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(REPO_ROOT / "skills" / "last30days" / "scripts"))
import last30days as cli
from lib import html_render, schema
@@ -297,6 +292,5 @@ class HtmlCliIntegrationTests(unittest.TestCase):
self.assertIn("comparing 2: OpenClaw, Hermes", saved)
self.assertNotIn("last30days · OpenClaw</title>", saved)
if __name__ == "__main__":
unittest.main()
-4
View File
@@ -1,11 +1,7 @@
import sys
import urllib.error
import unittest
from pathlib import Path
from unittest.mock import patch, MagicMock
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "skills" / "last30days" / "scripts"))
from lib import http
-5
View File
@@ -1,8 +1,4 @@
import sys
import unittest
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "skills" / "last30days" / "scripts"))
from lib.instagram import _parse_items
@@ -63,6 +59,5 @@ class TestExpandInstagramQueries(unittest.TestCase):
queries = expand_instagram_queries("Kanye West", "quick")
self.assertEqual(len(queries), 1)
if __name__ == "__main__":
unittest.main()
-4
View File
@@ -1,13 +1,10 @@
"""Tests for instagram.py — ScrapeCreators Instagram search module."""
import os
import sys
import unittest
from pathlib import Path
from unittest.mock import MagicMock, patch
# Add lib to path
sys.path.insert(0, str(Path(__file__).parent.parent / "skills" / "last30days" / "scripts"))
from lib import instagram
from lib.relevance import tokenize as _tokenize
@@ -253,6 +250,5 @@ class TestTranscriptTimeoutConfig(unittest.TestCase):
kwargs = mock_http_get.call_args.kwargs
self.assertEqual(kwargs["timeout"], 30.0)
if __name__ == "__main__":
unittest.main()
+16 -22
View File
@@ -5,11 +5,7 @@ tests exercise transitively but don't assert on directly. A regression in
any of these functions would silently degrade output quality.
"""
import sys
import unittest
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "skills" / "last30days" / "scripts"))
from lib import planner, rerank, render, signals, schema
@@ -34,11 +30,11 @@ def _candidate(source: str = "reddit", **kwargs) -> schema.Candidate:
defaults.update(kwargs)
return schema.Candidate(**defaults)
# ---------------------------------------------------------------------------
# rerank._fallback_tuple
# ---------------------------------------------------------------------------
class TestFallbackTuple(unittest.TestCase):
def test_returns_score_and_explanation(self):
@@ -58,11 +54,11 @@ class TestFallbackTuple(unittest.TestCase):
low = _candidate(local_relevance=0.1, freshness=50, source_quality=0.7)
self.assertGreater(rerank._fallback_tuple(high)[0], rerank._fallback_tuple(low)[0])
# ---------------------------------------------------------------------------
# rerank._normalized_rrf
# ---------------------------------------------------------------------------
class TestNormalizedRrf(unittest.TestCase):
def test_zero_input(self):
@@ -77,11 +73,11 @@ class TestNormalizedRrf(unittest.TestCase):
result = rerank._normalized_rrf(1.0)
self.assertLessEqual(result, 100.0)
# ---------------------------------------------------------------------------
# render._assess_data_freshness
# ---------------------------------------------------------------------------
class TestAssessDataFreshness(unittest.TestCase):
def _report(self, items_by_source: dict) -> schema.Report:
@@ -120,11 +116,11 @@ class TestAssessDataFreshness(unittest.TestCase):
result = render._assess_data_freshness(report)
self.assertIsNone(result)
# ---------------------------------------------------------------------------
# render._format_date
# ---------------------------------------------------------------------------
class TestFormatDate(unittest.TestCase):
def test_high_confidence_clean(self):
@@ -138,11 +134,11 @@ class TestFormatDate(unittest.TestCase):
def test_none_item(self):
self.assertIn("unknown", render._format_date(None).lower())
# ---------------------------------------------------------------------------
# render._format_actor
# ---------------------------------------------------------------------------
class TestFormatActor(unittest.TestCase):
def test_reddit_subreddit(self):
@@ -157,11 +153,11 @@ class TestFormatActor(unittest.TestCase):
item = _item(source="youtube", author="Fireship")
self.assertEqual(render._format_actor(item), "Fireship")
# ---------------------------------------------------------------------------
# render._format_engagement
# ---------------------------------------------------------------------------
class TestFormatEngagement(unittest.TestCase):
def test_reddit_format(self):
@@ -174,11 +170,11 @@ class TestFormatEngagement(unittest.TestCase):
item = _item(engagement={})
self.assertIsNone(render._format_engagement(item))
# ---------------------------------------------------------------------------
# render._format_corroboration
# ---------------------------------------------------------------------------
class TestFormatCorroboration(unittest.TestCase):
def test_multi_source(self):
@@ -191,11 +187,11 @@ class TestFormatCorroboration(unittest.TestCase):
c = _candidate(sources=["reddit"])
self.assertIsNone(render._format_corroboration(c))
# ---------------------------------------------------------------------------
# render._format_explanation
# ---------------------------------------------------------------------------
class TestFormatExplanation(unittest.TestCase):
def test_hides_fallback_sentinel(self):
@@ -206,11 +202,11 @@ class TestFormatExplanation(unittest.TestCase):
c = _candidate(explanation="Directly compares frameworks")
self.assertEqual(render._format_explanation(c), "Directly compares frameworks")
# ---------------------------------------------------------------------------
# render._fmt_pairs and _format_number
# ---------------------------------------------------------------------------
class TestFmtPairs(unittest.TestCase):
def test_basic(self):
@@ -231,11 +227,11 @@ class TestFormatNumber(unittest.TestCase):
def test_small_integer(self):
self.assertEqual(render._format_number(42), "42")
# ---------------------------------------------------------------------------
# render._truncate
# ---------------------------------------------------------------------------
class TestTruncate(unittest.TestCase):
def test_short_text(self):
@@ -246,11 +242,11 @@ class TestTruncate(unittest.TestCase):
self.assertTrue(result.endswith("..."))
self.assertEqual(len(result), 50)
# ---------------------------------------------------------------------------
# planner._normalize_subquery_weights
# ---------------------------------------------------------------------------
class TestNormalizeSubqueryWeights(unittest.TestCase):
def test_sums_to_one(self):
@@ -270,11 +266,11 @@ class TestNormalizeSubqueryWeights(unittest.TestCase):
normed = planner._normalize_subquery_weights(sqs)
self.assertAlmostEqual(normed[0].weight / normed[1].weight, 4.0)
# ---------------------------------------------------------------------------
# planner._normalize_weights
# ---------------------------------------------------------------------------
class TestNormalizeWeights(unittest.TestCase):
def test_sums_to_one(self):
@@ -285,11 +281,11 @@ class TestNormalizeWeights(unittest.TestCase):
result = planner._normalize_weights({"a": 2.0, "b": -1.0})
self.assertAlmostEqual(result["b"], 0.0)
# ---------------------------------------------------------------------------
# planner._trim_subqueries_for_depth
# ---------------------------------------------------------------------------
class TestTrimSubqueriesForDepth(unittest.TestCase):
def _sq(self, label: str = "primary", sources: list[str] = None) -> schema.SubQuery:
@@ -318,11 +314,11 @@ class TestTrimSubqueriesForDepth(unittest.TestCase):
# Deep comparison should also use capability expansion, not trim
self.assertGreaterEqual(len(result[0].sources), 4)
# ---------------------------------------------------------------------------
# signals.annotate_stream
# ---------------------------------------------------------------------------
class TestAnnotateStream(unittest.TestCase):
def test_attaches_metadata(self):
@@ -345,11 +341,11 @@ class TestAnnotateStream(unittest.TestCase):
annotated = signals.annotate_stream(items, "test query", "balanced_recent")
self.assertEqual(annotated[0].item_id, "high")
# ---------------------------------------------------------------------------
# signals.prune_low_relevance
# ---------------------------------------------------------------------------
class TestPruneLowRelevance(unittest.TestCase):
def test_removes_low_relevance_items(self):
@@ -369,11 +365,11 @@ class TestPruneLowRelevance(unittest.TestCase):
result = signals.prune_low_relevance(items, minimum=0.1)
self.assertEqual(len(result), 1) # fallback keeps all
# ---------------------------------------------------------------------------
# Bug fixes found by PR review agents
# ---------------------------------------------------------------------------
class TestDaysAgoZeroFalsy(unittest.TestCase):
"""render._assess_data_freshness must not treat days_ago=0 as falsy."""
@@ -455,7 +451,6 @@ class TestGenericEngagementFormatter(unittest.TestCase):
# Should contain numeric values, not dict keys as numbers
self.assertIn("500", result)
if __name__ == "__main__":
unittest.main()
@@ -521,7 +516,6 @@ class TestDefaultDepthDoesNotCapSources(unittest.TestCase):
self.assertLessEqual(len(plan.subqueries[0].sources), 3)
class TestRerankWeightBalance(unittest.TestCase):
"""Reranker weight must dominate over RRF when candidates have divergent quality."""
-5
View File
@@ -1,8 +1,4 @@
import sys
import unittest
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "skills" / "last30days" / "scripts"))
from lib import normalize
@@ -225,6 +221,5 @@ class NormalizeV3Tests(unittest.TestCase):
)
self.assertEqual([], normalized)
if __name__ == "__main__":
unittest.main()
-5
View File
@@ -1,11 +1,7 @@
import sys
import threading
import unittest
from pathlib import Path
from unittest.mock import patch
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "skills" / "last30days" / "scripts"))
from lib import pipeline
from lib import http
from lib import schema
@@ -1009,6 +1005,5 @@ class TestExcludeSourcesEndToEnd(unittest.TestCase):
self.assertNotIn("tiktok", sources)
self.assertNotIn("instagram", sources)
if __name__ == "__main__":
unittest.main()
-7
View File
@@ -1,16 +1,10 @@
# ruff: noqa: E402
"""Tests for planner.plan_query internal_subrun quiet mode."""
from __future__ import annotations
import io
import sys
import unittest
from contextlib import redirect_stderr
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(REPO_ROOT / "skills" / "last30days" / "scripts"))
from lib import planner
@@ -50,6 +44,5 @@ class PlannerQuietModeTests(unittest.TestCase):
# no planner-error indication.
self.assertGreater(len(plan.subqueries), 0)
if __name__ == "__main__":
unittest.main()
-5
View File
@@ -1,8 +1,4 @@
import sys
import unittest
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "skills" / "last30days" / "scripts"))
from lib import planner
@@ -452,6 +448,5 @@ class FallbackDefaultsTests(unittest.TestCase):
self.assertIn("LLM planning failed", output)
self.assertNotIn("No --plan passed", output)
if __name__ == "__main__":
unittest.main()
+1 -5
View File
@@ -1,16 +1,13 @@
import json
import sys
import tomllib
import unittest
from pathlib import Path
from lib.skill_meta import read_skill_version
ROOT = Path(__file__).resolve().parents[1]
SKILL_ROOT = ROOT / "skills" / "last30days"
sys.path.insert(0, str(SKILL_ROOT / "scripts"))
from lib.skill_meta import read_skill_version # noqa: E402
def _json(path: Path) -> dict:
return json.loads(path.read_text(encoding="utf-8"))
@@ -60,6 +57,5 @@ class TestPluginContract(unittest.TestCase):
self.assertEqual([], offenders)
if __name__ == "__main__":
unittest.main()
+14 -18
View File
@@ -1,19 +1,15 @@
"""Tests for polymarket.py - Polymarket prediction market search."""
import json
import sys
from pathlib import Path
from unittest.mock import Mock, patch
import pytest
sys.path.insert(0, str(Path(__file__).parent.parent / "skills" / "last30days" / "scripts"))
from lib import polymarket
# === Helper Functions ===
def create_mock_event(
event_id="evt-123",
title="Test Event",
@@ -60,9 +56,9 @@ def create_mock_market(
"liquidity": liquidity,
}
# === Tests for _extract_core_subject() ===
def test_extract_core_subject_basic():
"""Test basic subject extraction."""
result = polymarket._extract_core_subject("AI frameworks")
@@ -86,9 +82,9 @@ def test_extract_core_subject_multiple_prefixes():
result = polymarket._extract_core_subject("research AI models")
assert result == "AI models"
# === Tests for _expand_queries() ===
def test_expand_queries_basic():
"""Test basic query expansion."""
queries = polymarket._expand_queries("AI framework")
@@ -132,9 +128,9 @@ def test_expand_queries_cap_at_six():
assert len(queries) <= 6
# === Tests for _passes_topic_filter() ===
def test_passes_topic_filter_match():
"""Test that matching events pass the filter."""
assert polymarket._passes_topic_filter("AI safety", "AI Safety Conference 2026") is True
@@ -194,9 +190,9 @@ def test_passes_topic_filter_multi_word_edge_exactly_three():
"Tesla stock price", "Tesla quarterly earnings"
) is False # only "tesla" matches, needs 2
# === Tests for _parse_outcome_prices() ===
def test_parse_outcome_prices_basic():
"""Test basic outcome price parsing."""
market = {
@@ -259,9 +255,9 @@ def test_parse_outcome_prices_invalid_json():
assert result == []
# === Tests for _format_price_movement() ===
def test_format_price_movement_one_day():
"""Test formatting one-day price movement."""
market = {
@@ -322,9 +318,9 @@ def test_format_price_movement_missing_data():
assert result is None
# === Tests for _shorten_question() ===
def test_shorten_question_will_pattern():
"""Test shortening 'Will X...' questions."""
result = polymarket._shorten_question("Will Arizona win the NCAA Tournament?")
@@ -354,9 +350,9 @@ def test_shorten_question_long():
assert len(result) <= 40
# === Tests for search_polymarket() ===
def test_search_polymarket_result_cap():
"""Test that result cap configuration exists."""
assert "quick" in polymarket.RESULT_CAP
@@ -385,8 +381,9 @@ def test_search_polymarket_query_expansion():
# Should expand to multiple queries
assert len(queries) >= 2
@patch('lib.polymarket.http.post')
def test_search_polymarket_http_error_handling(mock_post):
"""Test graceful handling of HTTP errors."""
from lib.http import HTTPError
@@ -397,9 +394,9 @@ def test_search_polymarket_http_error_handling(mock_post):
# Should return structure with error
assert "events" in result or "error" in result
# === Tests for parse_polymarket_response() ===
def test_parse_polymarket_response_basic():
"""Test basic response parsing."""
response = {
@@ -472,9 +469,9 @@ def test_parse_polymarket_response_engagement():
# Check for volume or liquidity fields
assert "volume24hr" in items[0] or "liquidity" in items[0] or isinstance(items[0], dict)
# === Tests for engagement scoring ===
def test_engagement_with_volume():
"""Test engagement calculation with volume."""
response = {
@@ -491,9 +488,9 @@ def test_engagement_with_volume():
# volume24hr should be captured
assert "volume24hr" in engagement or isinstance(engagement, dict)
# === Tests for noise-word query skipping ===
def test_expand_queries_skips_noise_words():
"""Noise words like 'west' should not become standalone queries."""
queries = polymarket._expand_queries("kanye west")
@@ -520,9 +517,9 @@ def test_expand_queries_all_noise_words_keeps_phrase():
lowered = [q.lower() for q in queries]
assert "north" not in lowered or "north west" in lowered # only as part of phrase
# === Tests for per-item relevance floor ===
def test_per_item_relevance_floor_drops_zero_items():
"""Items with relevance 0.0 should be dropped even if best item is high."""
# Simulate the filtering logic directly
@@ -559,6 +556,5 @@ def test_per_item_relevance_floor_no_drops_when_all_high():
filtered = [i for i in items if i["relevance"] >= 0.10]
assert len(filtered) == 3
if __name__ == "__main__":
pytest.main([__file__, "-v"])
-7
View File
@@ -1,14 +1,8 @@
# ruff: noqa: E402
"""Tests for --polymarket-keywords filter and filter_items_against_keywords."""
from __future__ import annotations
import sys
import unittest
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(REPO_ROOT / "skills" / "last30days" / "scripts"))
from lib import polymarket
@@ -69,6 +63,5 @@ class FilterItemsAgainstKeywordsTests(unittest.TestCase):
out = polymarket.filter_items_against_keywords(items, ["nba", "gsw"])
self.assertEqual(out, [])
if __name__ == "__main__":
unittest.main()
-5
View File
@@ -6,11 +6,7 @@ public v3.0.8 and still returned junk for queries like 'birthday gift for
cannot bypass by skipping SKILL.md.
"""
import sys
import unittest
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "skills" / "last30days" / "scripts"))
from lib import preflight
@@ -123,6 +119,5 @@ class TestRefuseMessage(unittest.TestCase):
assert msg is not None
self.assertIn("birthday gift for 40 year old", msg)
if __name__ == "__main__":
unittest.main()
-5
View File
@@ -1,9 +1,5 @@
import json
import sys
import unittest
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "skills" / "last30days" / "scripts"))
from lib import providers
@@ -162,6 +158,5 @@ class TestParseCodexStream(unittest.TestCase):
def test_done_only_stream(self):
self.assertEqual({}, providers._parse_codex_stream("data: [DONE]\n\n"))
if __name__ == "__main__":
unittest.main()
+2 -7
View File
@@ -5,19 +5,14 @@ HN, Polymarket, Reddit (always active), X, YouTube.
ScrapeCreators adds TikTok + Instagram as bonus sources, not core.
"""
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "skills" / "last30days" / "scripts"))
import pytest
from unittest.mock import patch
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
def _base_config(**overrides):
"""Return a minimal config dict."""
config = {
@@ -52,11 +47,11 @@ def _compute(config_overrides=None, result_overrides=None, ytdlp_installed=False
with patch.object(youtube_yt, "is_ytdlp_installed", return_value=ytdlp_installed):
return compute_quality_score(config, results)
# ---------------------------------------------------------------------------
# Tests
# ---------------------------------------------------------------------------
class TestBaseline:
"""HN + Polymarket + Reddit always active (no X, no YT) -> 60%."""
-6
View File
@@ -1,10 +1,6 @@
"""Tests for query.py — shared query utilities."""
import sys
import unittest
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent / "skills" / "last30days" / "scripts"))
from lib.query import NOISE_WORDS, extract_compound_terms, extract_core_subject
@@ -126,7 +122,6 @@ class TestNoiseWordsCompleteness(unittest.TestCase):
self.assertIn(w, NOISE_WORDS)
class TestExtractCompoundTerms(unittest.TestCase):
"""Tests for extract_compound_terms()."""
@@ -148,6 +143,5 @@ class TestExtractCompoundTerms(unittest.TestCase):
self.assertIn("vc-backed", terms)
self.assertIn("start-up", terms)
if __name__ == "__main__":
unittest.main()
-5
View File
@@ -1,8 +1,4 @@
import sys
import unittest
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "skills" / "last30days" / "scripts"))
from lib import query
@@ -39,6 +35,5 @@ class QueryV3Tests(unittest.TestCase):
self.assertIn("Claude Code", terms)
self.assertIn("React Native", terms)
if __name__ == "__main__":
unittest.main()
-5
View File
@@ -1,8 +1,4 @@
import sys
import unittest
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "skills" / "last30days" / "scripts"))
from lib.reddit import (
_extract_date,
@@ -264,6 +260,5 @@ class TestEnrichmentBudget(unittest.TestCase):
enriched = [i for i in result if i.get("top_comments")]
self.assertEqual(len(enriched), 0)
if __name__ == "__main__":
unittest.main()
-3
View File
@@ -1,12 +1,10 @@
"""Tests for reddit_enrich.py — comment enrichment and parsing."""
import json
import sys
import unittest
from pathlib import Path
# Add lib to path
sys.path.insert(0, str(Path(__file__).parent.parent / "skills" / "last30days" / "scripts"))
from lib import reddit_enrich
@@ -124,6 +122,5 @@ class TestExtractCommentInsights(unittest.TestCase):
insights = reddit_enrich.extract_comment_insights(comments, limit=3)
self.assertLessEqual(len(insights), 3)
if __name__ == "__main__":
unittest.main()
+3 -7
View File
@@ -5,19 +5,16 @@ import urllib.error
from unittest import mock
import pytest
import sys
import os
# Ensure lib is importable
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "skills", "last30days", "scripts"))
from lib import reddit_public
# ---------------------------------------------------------------------------
# Fixtures / helpers
# ---------------------------------------------------------------------------
def _make_reddit_listing(posts):
"""Build a Reddit listing JSON structure from a list of post dicts."""
children = []
@@ -38,7 +35,6 @@ def _make_reddit_listing(posts):
})
return {"data": {"children": children}}
SAMPLE_LISTING = _make_reddit_listing([
{
"title": "Claude Code is amazing",
@@ -72,11 +68,11 @@ def _mock_urlopen_ok(listing_data):
resp.__exit__ = mock.MagicMock(return_value=False)
return resp
# ---------------------------------------------------------------------------
# Tests
# ---------------------------------------------------------------------------
class TestSearchReturnsCorrectFields:
"""Search query returns parsed results with correct fields."""
@@ -329,11 +325,11 @@ class TestMissingSubreddit:
results = reddit_public.search("test", subreddit="nonexistent")
assert results == []
# ---------------------------------------------------------------------------
# Tests for comment enrichment (Unit 2)
# ---------------------------------------------------------------------------
class TestEnrichmentIntegration:
"""search_reddit_public enriches top posts with comments."""
-4
View File
@@ -1,11 +1,8 @@
"""Tests for reddit.py — ScrapeCreators Reddit search module."""
import sys
import unittest
from pathlib import Path
# Add lib to path
sys.path.insert(0, str(Path(__file__).parent.parent / "skills" / "last30days" / "scripts"))
from lib import reddit
@@ -176,6 +173,5 @@ class TestPostRelevance(unittest.TestCase):
)
self.assertGreater(score, 0.7)
if __name__ == "__main__":
unittest.main()
-5
View File
@@ -3,11 +3,7 @@
Migrated from test_youtube_relevance.py + new hashtag/synonym tests.
"""
import sys
import unittest
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent / "skills" / "last30days" / "scripts"))
from lib.relevance import STOPWORDS, SYNONYMS, token_overlap_relevance, tokenize
@@ -126,6 +122,5 @@ class TestHashtagRelevance(unittest.TestCase):
rel2 = token_overlap_relevance("test query", "test content")
self.assertEqual(rel1, rel2)
if __name__ == "__main__":
unittest.main()
-5
View File
@@ -1,8 +1,4 @@
import sys
import unittest
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "skills" / "last30days" / "scripts"))
from lib import relevance
@@ -47,6 +43,5 @@ class RelevanceCoreV3Tests(unittest.TestCase):
)
self.assertGreater(score, 0.0)
if __name__ == "__main__":
unittest.main()
-7
View File
@@ -1,15 +1,9 @@
# ruff: noqa: E402
"""Tests for render.render_comparison_multi and emit_comparison_output."""
from __future__ import annotations
import json
import sys
import unittest
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(REPO_ROOT / "skills" / "last30days" / "scripts"))
import last30days as cli
from lib import render, schema
@@ -300,6 +294,5 @@ class EmitComparisonOutputTests(unittest.TestCase):
with self.assertRaises(SystemExit):
cli.emit_comparison_output(reports, emit="xml")
if __name__ == "__main__":
unittest.main()
-5
View File
@@ -1,8 +1,4 @@
import sys
import unittest
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "skills" / "last30days" / "scripts"))
from lib import render, schema
@@ -636,6 +632,5 @@ class YoutubeFooterTranscriptRatioTests(unittest.TestCase):
# No YouTube footer line at all - so no transcript segment either
self.assertNotIn("with transcripts", text)
if __name__ == "__main__":
unittest.main()
-6
View File
@@ -1,13 +1,7 @@
"""Tests for the fun judge heuristic fallback in rerank.py."""
import sys
from pathlib import Path
import pytest
SCRIPTS_DIR = Path(__file__).parent.parent / "skills" / "last30days" / "scripts"
sys.path.insert(0, str(SCRIPTS_DIR))
from lib import schema
from lib.rerank import _apply_single_fun_fallback, _extract_comment_text
-5
View File
@@ -1,8 +1,4 @@
import sys
import unittest
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "skills" / "last30days" / "scripts"))
from lib import rerank, schema
@@ -383,6 +379,5 @@ class ExpandedHaystackTests(unittest.TestCase):
# should not fire; final_score reflects only base signal.
self.assertNotIn("entity-miss", on_topic.explanation or "")
if __name__ == "__main__":
unittest.main()
-5
View File
@@ -1,12 +1,8 @@
import io
import sys
import unittest
from contextlib import redirect_stderr
from pathlib import Path
from unittest.mock import patch
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "skills" / "last30days" / "scripts"))
from lib import resolve
from lib.resolve import MAX_SUBS, _merge_category_peers
@@ -363,6 +359,5 @@ class AutoResolveCategoryIntegration(unittest.TestCase):
result = resolve.auto_resolve("test topic", {})
self.assertIsNone(result["category"])
if __name__ == "__main__":
unittest.main()
+12 -13
View File
@@ -9,10 +9,8 @@ from unittest.mock import patch
import pytest
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "skills" / "last30days"))
# Import the internal parser directly for testability (avoids platform check)
from scripts.lib.safari_cookies import (
from lib.safari_cookies import (
_parse_binary_cookies,
extract_safari_cookies_macos,
)
@@ -95,8 +93,9 @@ def _build_binary_cookies_file(pages: list[bytes]) -> bytes:
return data
@pytest.fixture
def x_cookies_file() -> bytes:
"""Build a minimal valid binary cookies file with .x.com cookies."""
rec1 = _build_cookie_record(".x.com", "auth_token", "test_auth_abc123")
@@ -153,8 +152,8 @@ class TestMultiplePages:
class TestErrorPaths:
def test_file_not_found(self, tmp_path: Path):
with patch(
"scripts.lib.safari_cookies.Path.home", return_value=tmp_path
), patch("scripts.lib.safari_cookies.sys") as mock_sys:
"lib.safari_cookies.Path.home", return_value=tmp_path
), patch("lib.safari_cookies.sys") as mock_sys:
mock_sys.platform = "darwin"
mock_sys.stderr = sys.stderr
result = extract_safari_cookies_macos("x.com", ["auth_token"])
@@ -183,8 +182,8 @@ class TestErrorPaths:
(legacy_dir / "Cookies.binarycookies").write_bytes(legacy_data)
with patch(
"scripts.lib.safari_cookies.Path.home", return_value=tmp_path
), patch("scripts.lib.safari_cookies.sys") as mock_sys:
"lib.safari_cookies.Path.home", return_value=tmp_path
), patch("lib.safari_cookies.sys") as mock_sys:
mock_sys.platform = "darwin"
mock_sys.stderr = sys.stderr
result = extract_safari_cookies_macos("x.com", ["auth_token", "ct0"])
@@ -215,8 +214,8 @@ class TestErrorPaths:
assert not sandbox_path.exists()
with patch(
"scripts.lib.safari_cookies.Path.home", return_value=tmp_path
), patch("scripts.lib.safari_cookies.sys") as mock_sys:
"lib.safari_cookies.Path.home", return_value=tmp_path
), patch("lib.safari_cookies.sys") as mock_sys:
mock_sys.platform = "darwin"
mock_sys.stderr = sys.stderr
result = extract_safari_cookies_macos("x.com", ["auth_token"])
@@ -239,8 +238,8 @@ class TestErrorPaths:
cookie_file.write_bytes(b"cook")
with patch(
"scripts.lib.safari_cookies.Path.home", return_value=tmp_path
), patch("scripts.lib.safari_cookies.sys") as mock_sys, patch.object(
"lib.safari_cookies.Path.home", return_value=tmp_path
), patch("lib.safari_cookies.sys") as mock_sys, patch.object(
Path, "read_bytes", side_effect=PermissionError
):
mock_sys.platform = "darwin"
@@ -275,7 +274,7 @@ class TestErrorPaths:
assert result is None
def test_non_darwin_returns_none(self):
with patch("scripts.lib.safari_cookies.sys") as mock_sys:
with patch("lib.safari_cookies.sys") as mock_sys:
mock_sys.platform = "linux"
result = extract_safari_cookies_macos("x.com", ["auth_token"])
assert result is None
-3
View File
@@ -1,4 +1,3 @@
# ruff: noqa: E402
"""Tests for per-entity save files when running vs-mode or --competitors.
Each entity's sub-run produces its own {entity-slug}-raw.md. Single-entity
@@ -15,7 +14,6 @@ import unittest
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(REPO_ROOT / "skills" / "last30days" / "scripts"))
def _engine_path() -> Path:
@@ -79,6 +77,5 @@ class PerEntitySaveFilesTests(unittest.TestCase):
self.assertIn("## Resolved Entities", content)
self.assertIn("**Anthropic**", content)
if __name__ == "__main__":
unittest.main()
-5
View File
@@ -1,8 +1,4 @@
import sys
import unittest
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "skills" / "last30days" / "scripts"))
from lib import schema
@@ -84,6 +80,5 @@ class SchemaV3Tests(unittest.TestCase):
self.assertEqual(0.0, item.source_quality)
self.assertEqual(0.0, item.local_rank_score)
if __name__ == "__main__":
unittest.main()
-5
View File
@@ -1,17 +1,12 @@
"""Tests for OpenClaw setup and device auth functions."""
import json
import sys
import time
from pathlib import Path
from unittest.mock import patch, MagicMock, call
import pytest
# Add scripts dir to path
SCRIPTS_DIR = Path(__file__).parent.parent / "skills" / "last30days" / "scripts"
sys.path.insert(0, str(SCRIPTS_DIR))
from lib import setup_wizard
-6
View File
@@ -1,17 +1,11 @@
"""Tests for the first-run setup wizard module."""
import os
import sys
import tempfile
from pathlib import Path
from unittest.mock import patch, MagicMock
import pytest
# Add scripts dir to path
SCRIPTS_DIR = Path(__file__).parent.parent / "skills" / "last30days" / "scripts"
sys.path.insert(0, str(SCRIPTS_DIR))
from lib import setup_wizard
-8
View File
@@ -1,9 +1,5 @@
import math
import sys
import unittest
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "skills" / "last30days" / "scripts"))
from lib import schema, signals
from lib.hackernews import parse_hackernews_response
@@ -232,7 +228,6 @@ class SignalsV3Tests(unittest.TestCase):
pruned = signals.prune_low_relevance([weak], minimum=0.1)
self.assertEqual(["weak"], [item.item_id for item in pruned])
# -- Iteration 1: HN engagement bug --
def test_hackernews_parse_emits_comments_key(self):
@@ -566,7 +561,6 @@ class SignalsV3Tests(unittest.TestCase):
self.assertIn("social", ids, "Item with relevance 0.05 should survive pruning")
self.assertIn("strong", ids)
# -- Unit 3: YouTube high-engagement relevance floor --
def test_youtube_high_engagement_gets_relevance_floor(self):
@@ -608,7 +602,6 @@ class SignalsV3Tests(unittest.TestCase):
rel = signals.local_relevance(item, "kanye west")
self.assertLess(rel, 0.3, f"Non-YouTube item should not get YouTube floor, got {rel}")
# -- Unit 8: Engagement floor for TikTok/Instagram --
def test_tiktok_below_1000_views_pruned(self):
@@ -709,6 +702,5 @@ class SignalsV3Tests(unittest.TestCase):
aspire_ids = [item.item_id for item in pruned if item.item_id.startswith("aspire")]
self.assertEqual(len(aspire_ids), 0, f"All @aspiresnippets items should be pruned, got {aspire_ids}")
if __name__ == "__main__":
unittest.main()
+1 -4
View File
@@ -6,15 +6,13 @@ regex coverage inside the helper could pass CI because render.py's fallback
to "?" swallows the signal.
"""
import sys
import tempfile
import unittest
from pathlib import Path
from lib.skill_meta import read_skill_version
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / "skills" / "last30days" / "scripts"))
from lib.skill_meta import read_skill_version # noqa: E402
class ReadSkillVersionTests(unittest.TestCase):
@@ -56,6 +54,5 @@ class ReadSkillVersionTests(unittest.TestCase):
path.write_bytes(bytes(range(128, 256)))
self.assertIsNone(read_skill_version(path))
if __name__ == "__main__":
unittest.main()
-4
View File
@@ -5,13 +5,10 @@ to SKILL.md frontmatter. These tests use monkeypatch to swap the render module's
__file__ attribute, which controls where the walk starts.
"""
import sys
import unittest
from pathlib import Path
from unittest.mock import patch
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "skills" / "last30days" / "scripts"))
from lib import render
@@ -132,6 +129,5 @@ class SkillVersionFallbackTests(unittest.TestCase):
with patch.object(render, "__file__", str(fake_render)):
self.assertEqual("5.5.5", render._skill_version())
if __name__ == "__main__":
unittest.main()
-5
View File
@@ -1,8 +1,4 @@
import sys
import unittest
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "skills" / "last30days" / "scripts"))
from lib import schema, snippet
@@ -58,6 +54,5 @@ class SnippetV3Tests(unittest.TestCase):
result = snippet.extract_best_snippet(item, "openclaw nanoclaw ironclaw", max_words=20)
self.assertIn("openclaw nanoclaw ironclaw", result)
if __name__ == "__main__":
unittest.main()
+9 -9
View File
@@ -9,14 +9,13 @@ from pathlib import Path
import pytest
# Import the module under test
import sys
sys.path.insert(0, str(Path(__file__).parent.parent / "skills" / "last30days" / "scripts"))
import store
from lib import schema
@pytest.fixture
def temp_db():
"""Create a temporary database for testing."""
with tempfile.NamedTemporaryFile(suffix=".db", delete=False) as f:
@@ -36,8 +35,9 @@ def temp_db():
if db_path.exists():
db_path.unlink()
@pytest.fixture
def sample_report():
"""Create a sample Report with multiple sources including HN and Polymarket."""
return schema.report_from_dict({
@@ -179,9 +179,9 @@ def sample_report():
"warnings": [],
})
# === Tests for findings_from_report() ===
def test_findings_from_report_processes_all_sources(sample_report):
"""Test that findings_from_report extracts items from all sources in items_by_source."""
findings = store.findings_from_report(sample_report)
@@ -324,9 +324,9 @@ def test_findings_from_report_handles_missing_fields():
assert f["relevance_score"] == 0.5
assert f["summary"] == "Content" # Falls back to body
# === Tests for store_findings() ===
def test_store_findings_basic(temp_db, sample_report):
"""Test basic storage of findings."""
topic = store.add_topic("Test Topic")
@@ -565,6 +565,7 @@ def test_store_findings_updates_existing_sighting_for_same_run(temp_db):
assert sightings[0]["source_title"] == "Reddit 1 updated"
assert sightings[0]["engagement_score"] == 15.0
def test_get_latest_completed_runs_returns_newest_completed_only(temp_db):
"""Test latest-run lookup ignores failed runs and orders newest first."""
topic = store.add_topic("Test Topic")
@@ -672,9 +673,9 @@ def test_update_validates_allowed_columns(temp_db, sample_report):
with pytest.raises(ValueError, match="invalid_finding_column"):
store.update_finding(finding_id, invalid_finding_column="x")
# === Tests for topic management ===
def test_add_topic(temp_db):
"""Test adding a topic."""
topic = store.add_topic("Test Topic", schedule="0 8 * * *")
@@ -742,9 +743,9 @@ def test_list_topics(temp_db):
assert "last_run" in topic
assert "last_status" in topic
# === Tests for get_new_findings() ===
def test_get_new_findings(temp_db, sample_report):
"""Test retrieving new findings for a topic."""
topic = store.add_topic("Test Topic")
@@ -779,6 +780,5 @@ def test_get_new_findings_filters_by_date(temp_db, sample_report):
assert len(new_findings) == 4
if __name__ == "__main__":
pytest.main([__file__, "-v"])
-5
View File
@@ -4,13 +4,9 @@ Covers the process-group cleanup path, timeout behavior, success path,
PID callback wiring, and environment inheritance.
"""
import sys
import unittest
from pathlib import Path
from unittest.mock import patch
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "scripts"))
from lib import subproc
@@ -97,6 +93,5 @@ class TestRunWithTimeout(unittest.TestCase):
self.assertEqual(result.returncode, 0)
self.assertEqual(result.stdout.strip(), "ok")
if __name__ == "__main__":
unittest.main()
-5
View File
@@ -1,8 +1,4 @@
import sys
import unittest
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "skills" / "last30days" / "scripts"))
from lib.tiktok import _parse_items
@@ -234,6 +230,5 @@ class TestTikTokEnrichWithComments(unittest.TestCase):
self.assertIn("top_comments", by_id["mid"])
self.assertNotIn("top_comments", by_id["low"])
if __name__ == "__main__":
unittest.main()
-5
View File
@@ -1,11 +1,7 @@
"""Tests for Truth Social source module."""
import sys
import unittest
from pathlib import Path
from unittest.mock import patch, MagicMock
sys.path.insert(0, str(Path(__file__).parent.parent / "skills" / "last30days" / "scripts"))
from lib import truthsocial
@@ -229,6 +225,5 @@ class TestParseTruthSocialResponse(unittest.TestCase):
self.assertNotIn("<", items[0]["text"])
self.assertNotIn(">", items[0]["text"])
if __name__ == "__main__":
unittest.main()
-6
View File
@@ -1,13 +1,8 @@
# ruff: noqa: E402
import io
import sys
import unittest
from contextlib import redirect_stderr
from pathlib import Path
from unittest import mock
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "skills" / "last30days" / "scripts"))
from lib import ui
@@ -72,6 +67,5 @@ class UiV3Tests(unittest.TestCase):
self.assertIn("Truth Social: 1 post", output)
self.assertIn("Xiaohongshu: 4 posts", output)
if __name__ == "__main__":
unittest.main()
+1 -5
View File
@@ -1,15 +1,12 @@
import re
import sys
import unittest
from pathlib import Path
from lib.skill_meta import read_skill_version
ROOT = Path(__file__).resolve().parents[1]
SKILL_ROOT = ROOT / "skills" / "last30days"
sys.path.insert(0, str(SKILL_ROOT / "scripts"))
from lib.skill_meta import read_skill_version # noqa: E402
def _skill_version() -> str:
version = read_skill_version(SKILL_ROOT / "SKILL.md")
@@ -77,6 +74,5 @@ class TestVersionConsistency(unittest.TestCase):
self.assertEqual([], offenders)
if __name__ == "__main__":
unittest.main()
-7
View File
@@ -1,4 +1,3 @@
# ruff: noqa: E402
"""Tests for vs-mode routing into the competitor fanout.
A topic containing " vs " / " versus " triggers N-pass fanout (not the
@@ -9,13 +8,8 @@ pipeline.run() with its own Step 0.55 targeting.
from __future__ import annotations
import io
import sys
import unittest
from contextlib import redirect_stderr
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(REPO_ROOT / "skills" / "last30days" / "scripts"))
from lib import planner
@@ -58,6 +52,5 @@ class VsModeEntityDetectionTests(unittest.TestCase):
result = planner._comparison_entities("Drake vs Drake")
self.assertEqual(result, ["Drake"])
if __name__ == "__main__":
unittest.main()
+23 -18
View File
@@ -3,21 +3,19 @@
import json
import sqlite3
import subprocess
import sys
import tempfile
from pathlib import Path
from unittest.mock import Mock, patch
import pytest
sys.path.insert(0, str(Path(__file__).parent.parent / "skills" / "last30days" / "scripts"))
import store
import watchlist
from lib import schema
@pytest.fixture
def temp_db():
"""Create a temporary database for testing."""
with tempfile.NamedTemporaryFile(suffix=".db", delete=False) as f:
@@ -37,9 +35,9 @@ def temp_db():
if db_path.exists():
db_path.unlink()
# === Tests for cmd_add() ===
def test_cmd_add_basic(temp_db, capsys):
"""Test adding a topic with default schedule."""
args = Mock()
@@ -104,9 +102,9 @@ def test_cmd_add_with_search_queries(temp_db, capsys):
queries = json.loads(topic["search_queries"])
assert queries == ["query1", "query2", "query3"]
# === Tests for cmd_remove() ===
def test_cmd_remove_existing_topic(temp_db, capsys):
"""Test removing an existing topic."""
# Add a topic first
@@ -139,9 +137,9 @@ def test_cmd_remove_nonexistent_topic(temp_db, capsys):
assert output["action"] == "not_found"
assert output["topic"] == "Nonexistent Topic"
# === Tests for cmd_list() ===
def test_cmd_list_empty(temp_db, capsys):
"""Test listing when no topics exist."""
args = Mock()
@@ -176,9 +174,9 @@ def test_cmd_list_with_topics(temp_db, capsys):
topic_names = {t["name"] for t in output["topics"]}
assert topic_names == {"Topic 1", "Topic 2", "Topic 3"}
# === Tests for cmd_delta() ===
def test_cmd_delta_outputs_topic_delta(temp_db, capsys):
"""Test printing the latest watchlist delta as JSON."""
topic = store.add_topic("Test Topic")
@@ -231,9 +229,9 @@ def test_cmd_delta_unknown_topic_exits(temp_db):
with pytest.raises(SystemExit):
watchlist.cmd_delta(args)
# === Tests for cmd_config() ===
def test_cmd_config_delivery(temp_db, capsys):
"""Test configuring delivery channel."""
args = Mock()
@@ -276,10 +274,11 @@ def test_cmd_config_unknown_key(temp_db):
with pytest.raises(SystemExit):
watchlist.cmd_config(args)
# === Tests for _run_topic() ===
@patch('watchlist.subprocess.run')
def test_run_topic_success(mock_subprocess, temp_db):
"""Test successful topic run."""
topic = store.add_topic("Test Topic")
@@ -364,8 +363,9 @@ def test_run_topic_success(mock_subprocess, temp_db):
assert result["new"] == 1
assert result["topic"] == "Test Topic"
@patch('watchlist.subprocess.run')
def test_run_topic_failure(mock_subprocess, temp_db):
"""Test topic run failure."""
topic = store.add_topic("Test Topic")
@@ -381,8 +381,9 @@ def test_run_topic_failure(mock_subprocess, temp_db):
assert result["status"] == "failed"
assert "Error message" in result["error"]
@patch('watchlist.subprocess.run')
def test_run_topic_timeout(mock_subprocess, temp_db):
"""Test topic run timeout."""
topic = store.add_topic("Test Topic")
@@ -395,9 +396,10 @@ def test_run_topic_timeout(mock_subprocess, temp_db):
assert result["status"] == "failed"
assert result["error"] == "timeout"
@patch('watchlist.subprocess.run')
@patch('watchlist._deliver_findings')
def test_run_topic_calls_delivery(mock_deliver, mock_subprocess, temp_db):
"""Test that successful run calls delivery."""
topic = store.add_topic("Test Topic")
@@ -484,10 +486,11 @@ def test_run_topic_calls_delivery(mock_deliver, mock_subprocess, temp_db):
assert call_args[0] == "Test Topic"
assert call_args[1]["new"] == 1
# === Tests for cmd_run_one() ===
@patch('watchlist._run_topic')
def test_cmd_run_one(mock_run, temp_db, capsys):
"""Test running a single topic."""
topic = store.add_topic("Test Topic")
@@ -521,10 +524,11 @@ def test_cmd_run_one_nonexistent_topic(temp_db, capsys):
with pytest.raises(SystemExit):
watchlist.cmd_run_one(args)
# === Tests for cmd_run_all() ===
@patch('watchlist._run_topic')
def test_cmd_run_all_no_topics(mock_run, temp_db, capsys):
"""Test running all topics when none exist."""
args = Mock()
@@ -537,8 +541,9 @@ def test_cmd_run_all_no_topics(mock_run, temp_db, capsys):
assert "No enabled topics" in output["message"]
@patch('watchlist._run_topic')
def test_cmd_run_all_multiple_topics(mock_run, temp_db, capsys):
"""Test running multiple topics."""
# Add topics
@@ -564,9 +569,10 @@ def test_cmd_run_all_multiple_topics(mock_run, temp_db, capsys):
assert output["action"] == "run_all"
assert len(output["results"]) == 2
@patch('watchlist._run_topic')
@patch('watchlist.store.get_daily_cost')
def test_cmd_run_all_respects_budget(mock_cost, mock_run, temp_db, capsys):
"""Test that run-all respects daily budget."""
# Add topics
@@ -599,6 +605,5 @@ def test_cmd_run_all_respects_budget(mock_cost, mock_run, temp_db, capsys):
assert len(skipped) == 3 # All 3 topics skipped
if __name__ == "__main__":
pytest.main([__file__, "-v"])
+23 -17
View File
@@ -1,19 +1,15 @@
"""Tests for watchlist.py delivery functions (PR #86 feature)."""
import sys
from pathlib import Path
from unittest.mock import patch
import pytest
sys.path.insert(0, str(Path(__file__).parent.parent / "skills" / "last30days" / "scripts"))
import watchlist
from lib.http import HTTPError
# === Tests for _format_delivery_message() ===
def test_format_message_announce_mode():
"""Test announce mode formatting (default mode with emoji)."""
message = watchlist._format_delivery_message(
@@ -56,10 +52,11 @@ def test_format_message_handles_zero_counts():
assert "0 new" in message
assert "0 updated" in message
# === Tests for _send_slack_webhook() ===
@patch('watchlist.http.post')
def test_send_slack_webhook_format(mock_post):
"""Test that Slack webhook uses correct format."""
watchlist._send_slack_webhook(
@@ -74,8 +71,9 @@ def test_send_slack_webhook_format(mock_post):
assert call_args[1]["json_data"] == {"text": "Test message"}
assert call_args[1]["timeout"] == 10
@patch('watchlist.http.post')
def test_send_slack_webhook_raises_on_error(mock_post):
"""Test that Slack webhook raises on HTTP error."""
mock_post.side_effect = HTTPError("HTTP 400", 400)
@@ -86,10 +84,11 @@ def test_send_slack_webhook_raises_on_error(mock_post):
"Test message"
)
# === Tests for _send_generic_webhook() ===
@patch('watchlist.http.post')
def test_send_generic_webhook_format(mock_post):
"""Test that generic webhook uses correct format."""
watchlist._send_generic_webhook(
@@ -108,8 +107,9 @@ def test_send_generic_webhook_format(mock_post):
assert "timestamp" in json_data
assert isinstance(json_data["timestamp"], float)
@patch('watchlist.http.post')
def test_send_generic_webhook_raises_on_error(mock_post):
"""Test that generic webhook raises on HTTP error."""
mock_post.side_effect = HTTPError("HTTP 500", 500)
@@ -120,11 +120,12 @@ def test_send_generic_webhook_raises_on_error(mock_post):
"Test message"
)
# === Tests for _deliver_findings() ===
@patch('watchlist.store.get_setting')
@patch('watchlist.http.post')
def test_deliver_findings_sends_when_new_greater_than_zero(mock_post, mock_get_setting):
"""Test that delivery fires when new > 0."""
mock_get_setting.side_effect = lambda key, default="": {
@@ -136,9 +137,10 @@ def test_deliver_findings_sends_when_new_greater_than_zero(mock_post, mock_get_s
assert mock_post.called
@patch('watchlist.store.get_setting')
@patch('watchlist.http.post')
def test_deliver_findings_skips_when_new_is_zero(mock_post, mock_get_setting):
"""Test that delivery is skipped when new=0."""
mock_get_setting.side_effect = lambda key, default="": {
@@ -150,9 +152,10 @@ def test_deliver_findings_skips_when_new_is_zero(mock_post, mock_get_setting):
assert not mock_post.called
@patch('watchlist.store.get_setting')
@patch('watchlist.http.post')
def test_deliver_findings_skips_when_channel_empty(mock_post, mock_get_setting):
"""Test that delivery is skipped when delivery_channel is empty."""
mock_get_setting.side_effect = lambda key, default="": {
@@ -164,9 +167,10 @@ def test_deliver_findings_skips_when_channel_empty(mock_post, mock_get_setting):
assert not mock_post.called
@patch('watchlist.store.get_setting')
@patch('watchlist.http.post')
def test_deliver_findings_uses_slack_format_for_slack_urls(mock_post, mock_get_setting):
"""Test that Slack URLs trigger Slack-specific format."""
mock_get_setting.side_effect = lambda key, default="": {
@@ -180,9 +184,10 @@ def test_deliver_findings_uses_slack_format_for_slack_urls(mock_post, mock_get_s
assert "text" in json_data
assert "Test Topic" in json_data["text"]
@patch('watchlist.store.get_setting')
@patch('watchlist.http.post')
def test_deliver_findings_uses_generic_format_for_other_urls(mock_post, mock_get_setting):
"""Test that non-Slack URLs trigger generic format."""
mock_get_setting.side_effect = lambda key, default="": {
@@ -197,9 +202,10 @@ def test_deliver_findings_uses_generic_format_for_other_urls(mock_post, mock_get
assert "source" in json_data
assert "timestamp" in json_data
@patch('watchlist.store.get_setting')
@patch('watchlist.http.post')
def test_deliver_findings_handles_failure_gracefully(mock_post, mock_get_setting, capsys):
"""Test that delivery failures don't crash the process."""
mock_get_setting.side_effect = lambda key, default="": {
@@ -215,9 +221,10 @@ def test_deliver_findings_handles_failure_gracefully(mock_post, mock_get_setting
captured = capsys.readouterr()
assert "Delivery failed" in captured.err
@patch('watchlist.store.get_setting')
@patch('watchlist.http.post')
def test_deliver_findings_respects_delivery_mode(mock_post, mock_get_setting):
"""Test that different delivery modes produce different messages."""
mock_get_setting.side_effect = lambda key, default="": {
@@ -240,6 +247,5 @@ def test_deliver_findings_respects_delivery_mode(mock_post, mock_get_setting):
silent_message = mock_post.call_args[1]["json_data"]["message"]
assert "📰" not in silent_message
if __name__ == "__main__":
pytest.main([__file__, "-v"])
-5
View File
@@ -1,9 +1,5 @@
import json
import sys
import unittest
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "skills" / "last30days" / "scripts"))
from lib.xai_x import parse_x_response
@@ -63,6 +59,5 @@ class TestXaiXEngagementZero(unittest.TestCase):
self.assertEqual(1, eng["replies"])
self.assertEqual(0, eng["quotes"])
if __name__ == "__main__":
unittest.main()
-5
View File
@@ -1,10 +1,6 @@
import sys
import unittest
from pathlib import Path
from unittest.mock import patch
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "skills" / "last30days" / "scripts"))
from lib.xquik import (
DEPTH_CONFIG,
_parse_tweet,
@@ -270,6 +266,5 @@ class TestDepthConfig(unittest.TestCase):
def test_deep_has_most_queries(self):
self.assertGreater(DEPTH_CONFIG["deep"]["queries"], DEPTH_CONFIG["quick"]["queries"])
if __name__ == "__main__":
unittest.main()
+5 -10
View File
@@ -1,20 +1,16 @@
"""Tests for xurl_x module."""
import json
import sys
import unittest
from pathlib import Path
from unittest import mock
sys.path.insert(0, str(Path(__file__).parent.parent / "skills" / "last30days" / "scripts"))
from lib import xurl_x
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
def _make_api_response(tweets=None, users=None):
"""Build a minimal X API v2 search/recent response."""
tweets = tweets or []
@@ -24,11 +20,11 @@ def _make_api_response(tweets=None, users=None):
resp["includes"] = {"users": users}
return resp
# ---------------------------------------------------------------------------
# is_available
# ---------------------------------------------------------------------------
class TestIsAvailable(unittest.TestCase):
def test_returns_true_when_xurl_authenticated(self):
completed = mock.Mock(returncode=0, stdout='{"username": "testuser"}')
@@ -62,11 +58,11 @@ class TestIsAvailable(unittest.TestCase):
with mock.patch("subprocess.run", return_value=completed):
self.assertFalse(xurl_x.is_available())
# ---------------------------------------------------------------------------
# search_x
# ---------------------------------------------------------------------------
class TestSearchX(unittest.TestCase):
def test_returns_parsed_json_on_success(self):
payload = {"data": [{"id": "1", "text": "hello world", "author_id": "u1"}]}
@@ -127,11 +123,11 @@ class TestSearchX(unittest.TestCase):
n_idx = call_args.index("-n")
self.assertEqual(int(call_args[n_idx + 1]), xurl_x.DEPTH_CONFIG["default"])
# ---------------------------------------------------------------------------
# parse_x_response
# ---------------------------------------------------------------------------
class TestParseXResponse(unittest.TestCase):
def _tweet(self, id_, text, author_id, created_at=None, metrics=None):
t = {"id": id_, "text": text, "author_id": author_id}
@@ -240,11 +236,11 @@ class TestParseXResponse(unittest.TestCase):
items = xurl_x.parse_x_response(resp)
self.assertEqual(items[0]["why_relevant"], "")
# ---------------------------------------------------------------------------
# DEPTH_CONFIG
# ---------------------------------------------------------------------------
class TestDepthConfig(unittest.TestCase):
def test_all_standard_depths_present(self):
for depth in ("quick", "default", "deep"):
@@ -256,6 +252,5 @@ class TestDepthConfig(unittest.TestCase):
xurl_x.DEPTH_CONFIG["quick"],
)
if __name__ == "__main__":
unittest.main()
-4
View File
@@ -1,11 +1,8 @@
"""Tests for YouTube relevance scoring."""
import sys
import unittest
from pathlib import Path
# Add lib to path
sys.path.insert(0, str(Path(__file__).parent.parent / "skills" / "last30days" / "scripts"))
from lib.relevance import token_overlap_relevance as _compute_relevance, tokenize as _tokenize
@@ -105,6 +102,5 @@ class TestComputeRelevance(unittest.TestCase):
result = _compute_relevance("Seedance", "Random cooking video")
self.assertEqual(result, 0.0)
if __name__ == "__main__":
unittest.main()
-5
View File
@@ -2,15 +2,11 @@
import json
import os
import sys
import tempfile
import unittest
import urllib.error
from pathlib import Path
from unittest import mock
sys.path.insert(0, str(Path(__file__).parent.parent / "skills" / "last30days" / "scripts"))
from lib import youtube_yt
@@ -536,6 +532,5 @@ class TestYtdlpSSHRouting(unittest.TestCase):
self.assertIn("--ignore-config", cmd[5])
self.assertIn("--no-cookies-from-browser", cmd[5])
if __name__ == "__main__":
unittest.main()