2860fb5d18
Security / Dependency review (push) Has been skipped
Scorecard / Scorecard analysis (push) Failing after 0s
Validate / eval (push) Failing after 0s
Security / Dependency audit (push) Failing after 1s
Security / Secret scan (push) Failing after 1s
Validate / tests (push) Failing after 0s
Validate / mcp-tests (push) Failing after 1s
GitHub Actions Security Analysis with zizmor 🌈 / zizmor (push) Failing after 1s
Security / SAST scan (push) Failing after 13m52s
212 lines
8.2 KiB
Python
212 lines
8.2 KiB
Python
"""Tests for --competitors-plan JSON parsing and per-entity kwargs threading."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import io
|
|
import json
|
|
import tempfile
|
|
import unittest
|
|
from contextlib import redirect_stderr
|
|
from pathlib import Path
|
|
from unittest import mock
|
|
|
|
import last30days as cli
|
|
|
|
|
|
class ParseCompetitorsPlanTests(unittest.TestCase):
|
|
def test_none_returns_empty(self):
|
|
self.assertEqual(cli.parse_competitors_plan(None), {})
|
|
|
|
def test_empty_string_returns_empty(self):
|
|
self.assertEqual(cli.parse_competitors_plan(""), {})
|
|
|
|
def test_inline_json_parsed(self):
|
|
raw = '{"Drake": {"x_handle": "Drake", "subreddits": ["Drizzy"]}}'
|
|
out = cli.parse_competitors_plan(raw)
|
|
self.assertIn("drake", out)
|
|
self.assertEqual(out["drake"]["x_handle"], "Drake")
|
|
self.assertEqual(out["drake"]["subreddits"], ["Drizzy"])
|
|
|
|
def test_file_path_accepted(self):
|
|
with tempfile.NamedTemporaryFile(
|
|
mode="w", suffix=".json", delete=False,
|
|
) as f:
|
|
json.dump(
|
|
{"Anthropic": {"x_handle": "AnthropicAI", "github_user": "anthropics"}},
|
|
f,
|
|
)
|
|
path = f.name
|
|
try:
|
|
out = cli.parse_competitors_plan(path)
|
|
self.assertEqual(out["anthropic"]["x_handle"], "AnthropicAI")
|
|
self.assertEqual(out["anthropic"]["github_user"], "anthropics")
|
|
finally:
|
|
Path(path).unlink(missing_ok=True)
|
|
|
|
def test_file_with_non_ascii_content(self):
|
|
"""Plan file with non-ASCII characters (e.g. accented names) reads without UnicodeDecodeError."""
|
|
with tempfile.NamedTemporaryFile(
|
|
mode="wb", suffix=".json", delete=False,
|
|
) as f:
|
|
f.write(
|
|
b'{"Nestl\xc3\xa9": {"x_handle": "Nestle", "subreddits": ["nestle"]}}'
|
|
)
|
|
path = f.name
|
|
try:
|
|
out = cli.parse_competitors_plan(path)
|
|
self.assertIn("nestlé", out)
|
|
self.assertEqual(out["nestlé"]["x_handle"], "Nestle")
|
|
finally:
|
|
Path(path).unlink(missing_ok=True)
|
|
|
|
def test_case_insensitive_key_normalization(self):
|
|
raw = '{"DRAKE": {"x_handle": "Drake"}}'
|
|
out = cli.parse_competitors_plan(raw)
|
|
self.assertIn("drake", out)
|
|
self.assertNotIn("DRAKE", out)
|
|
|
|
def test_unknown_fields_warned_and_ignored(self):
|
|
raw = '{"Drake": {"x_handle": "Drake", "bogus_field": 42}}'
|
|
err = io.StringIO()
|
|
with redirect_stderr(err):
|
|
out = cli.parse_competitors_plan(raw)
|
|
self.assertIn("drake", out)
|
|
self.assertNotIn("bogus_field", out["drake"])
|
|
self.assertIn("Unknown fields", err.getvalue())
|
|
|
|
def test_malformed_json_exits_2(self):
|
|
with self.assertRaises(SystemExit) as cm, redirect_stderr(io.StringIO()) as err:
|
|
cli.parse_competitors_plan("{not valid json")
|
|
self.assertEqual(cm.exception.code, 2)
|
|
self.assertIn("Invalid JSON", err.getvalue())
|
|
|
|
def test_top_level_list_rejected(self):
|
|
with self.assertRaises(SystemExit) as cm, redirect_stderr(io.StringIO()):
|
|
cli.parse_competitors_plan('["Drake", "Kendrick"]')
|
|
self.assertEqual(cm.exception.code, 2)
|
|
|
|
def test_entry_non_dict_skipped_with_warning(self):
|
|
raw = '{"Drake": "not-a-dict", "Kendrick": {"x_handle": "kendricklamar"}}'
|
|
err = io.StringIO()
|
|
with redirect_stderr(err):
|
|
out = cli.parse_competitors_plan(raw)
|
|
self.assertNotIn("drake", out)
|
|
self.assertIn("kendrick", out)
|
|
self.assertIn("must be a dict", err.getvalue())
|
|
|
|
def test_all_six_fields_accepted(self):
|
|
raw = json.dumps({
|
|
"OpenAI": {
|
|
"x_handle": "OpenAI",
|
|
"x_related": ["sama", "gdb"],
|
|
"subreddits": ["OpenAI", "MachineLearning"],
|
|
"github_user": "openai",
|
|
"github_repos": ["openai/gpt-5"],
|
|
"context": "GPT-5 launch imminent",
|
|
}
|
|
})
|
|
out = cli.parse_competitors_plan(raw)
|
|
entry = out["openai"]
|
|
self.assertEqual(entry["x_handle"], "OpenAI")
|
|
self.assertEqual(entry["x_related"], ["sama", "gdb"])
|
|
self.assertEqual(entry["subreddits"], ["OpenAI", "MachineLearning"])
|
|
self.assertEqual(entry["github_user"], "openai")
|
|
self.assertEqual(entry["github_repos"], ["openai/gpt-5"])
|
|
self.assertEqual(entry["context"], "GPT-5 launch imminent")
|
|
|
|
|
|
class SubrunKwargsForTests(unittest.TestCase):
|
|
def test_plan_wins_over_auto_resolve(self):
|
|
plan_entry = {"x_handle": "Drake", "subreddits": ["Drizzy"]}
|
|
resolved = {"x_handle": "wrong", "subreddits": ["wrong"]}
|
|
kwargs = cli.subrun_kwargs_for("Drake", plan_entry, resolved=resolved)
|
|
self.assertEqual(kwargs["x_handle"], "Drake")
|
|
self.assertEqual(kwargs["subreddits"], ["Drizzy"])
|
|
|
|
def test_auto_resolve_used_when_plan_missing(self):
|
|
resolved = {
|
|
"x_handle": "Drake",
|
|
"subreddits": ["Drizzy", "hiphopheads"],
|
|
"github_user": "",
|
|
"github_repos": [],
|
|
}
|
|
kwargs = cli.subrun_kwargs_for("Drake", {}, resolved=resolved)
|
|
self.assertEqual(kwargs["x_handle"], "Drake")
|
|
self.assertEqual(kwargs["subreddits"], ["Drizzy", "hiphopheads"])
|
|
|
|
def test_both_empty_yields_all_none(self):
|
|
kwargs = cli.subrun_kwargs_for("Drake", {}, resolved={})
|
|
self.assertIsNone(kwargs["x_handle"])
|
|
self.assertIsNone(kwargs["subreddits"])
|
|
self.assertIsNone(kwargs["github_user"])
|
|
self.assertIsNone(kwargs["github_repos"])
|
|
self.assertIsNone(kwargs["x_related"])
|
|
self.assertEqual(kwargs["_context"], "")
|
|
|
|
def test_trustpilot_domain_plan_wins_and_is_not_hint(self):
|
|
kwargs = cli.subrun_kwargs_for(
|
|
"ThriftBooks",
|
|
{"trustpilot_domain": "www.thriftbooks.com"},
|
|
resolved={"trustpilot_domain": "wrong.com"},
|
|
)
|
|
self.assertEqual(kwargs["trustpilot_domain"], "www.thriftbooks.com")
|
|
self.assertFalse(kwargs["_trustpilot_domain_is_hint"])
|
|
|
|
def test_trustpilot_domain_from_auto_resolve_is_hint(self):
|
|
kwargs = cli.subrun_kwargs_for(
|
|
"ThriftBooks", {}, resolved={"trustpilot_domain": "thriftbooks.com"},
|
|
)
|
|
self.assertEqual(kwargs["trustpilot_domain"], "thriftbooks.com")
|
|
self.assertTrue(kwargs["_trustpilot_domain_is_hint"])
|
|
|
|
def test_trustpilot_domain_absent_is_none(self):
|
|
kwargs = cli.subrun_kwargs_for("ThriftBooks", {}, resolved={})
|
|
self.assertIsNone(kwargs["trustpilot_domain"])
|
|
self.assertFalse(kwargs["_trustpilot_domain_is_hint"])
|
|
|
|
def test_x_handle_strips_at_sign(self):
|
|
kwargs = cli.subrun_kwargs_for(
|
|
"Drake", {"x_handle": "@Drake"}, resolved={},
|
|
)
|
|
self.assertEqual(kwargs["x_handle"], "Drake")
|
|
|
|
def test_subreddits_strip_r_prefix(self):
|
|
kwargs = cli.subrun_kwargs_for(
|
|
"Drake", {"subreddits": ["r/Drizzy", "hiphopheads"]}, resolved={},
|
|
)
|
|
self.assertEqual(kwargs["subreddits"], ["Drizzy", "hiphopheads"])
|
|
|
|
def test_github_repos_filter_non_slash(self):
|
|
kwargs = cli.subrun_kwargs_for(
|
|
"Drake",
|
|
{"github_repos": ["drake/ovo", "not-a-repo"]},
|
|
resolved={},
|
|
)
|
|
self.assertEqual(kwargs["github_repos"], ["drake/ovo"])
|
|
|
|
def test_x_related_list_normalized(self):
|
|
kwargs = cli.subrun_kwargs_for(
|
|
"Drake",
|
|
{"x_related": ["@pnd", "drakefan"]},
|
|
resolved={},
|
|
)
|
|
self.assertEqual(kwargs["x_related"], ["pnd", "drakefan"])
|
|
|
|
def test_github_user_lowercased(self):
|
|
kwargs = cli.subrun_kwargs_for(
|
|
"OpenAI", {"github_user": "@OpenAI"}, resolved={},
|
|
)
|
|
self.assertEqual(kwargs["github_user"], "openai")
|
|
|
|
def test_context_from_plan_or_resolved(self):
|
|
plan_entry = {"context": "Plan context"}
|
|
resolved = {"context": "Resolved context"}
|
|
kwargs = cli.subrun_kwargs_for("X", plan_entry, resolved=resolved)
|
|
self.assertEqual(kwargs["_context"], "Plan context")
|
|
|
|
kwargs = cli.subrun_kwargs_for("X", {}, resolved=resolved)
|
|
self.assertEqual(kwargs["_context"], "Resolved context")
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|