| 1 | """Documentation contract for Python CLI and wrapper-only flags.""" |
| 2 | |
| 3 | from __future__ import annotations |
| 4 | |
| 5 | from pathlib import Path |
| 6 | |
| 7 | import last30days as cli |
| 8 | |
| 9 | ROOT = Path(__file__).resolve().parents[1] |
| 10 | CONFIGURATION = ROOT / "CONFIGURATION.md" |
| 11 | SKILL_MD = ROOT / "skills" / "last30days" / "SKILL.md" |
| 12 | HTML_REFERENCE = ROOT / "skills" / "last30days" / "references" / "save-html-brief.md" |
| 13 | |
| 14 | |
| 15 | def _parser_flags() -> set[str]: |
| 16 | parser = cli.build_parser() |
| 17 | flags: set[str] = set() |
| 18 | for action in parser._actions: |
| 19 | flags.update(action.option_strings) |
| 20 | return flags |
| 21 | |
| 22 | |
| 23 | def test_configuration_documents_new_safety_flags(): |
| 24 | text = CONFIGURATION.read_text(encoding="utf-8") |
| 25 | flags = _parser_flags() |
| 26 | assert "--no-browser-cookies" in flags |
| 27 | assert "--no-browser-cookies" in text |
| 28 | assert "--preflight" in flags |
| 29 | assert "--preflight" in text |
| 30 | assert "--save-dir" in text |
| 31 | assert "--output" in text |
| 32 | assert "--publish-html" in flags |
| 33 | assert "--publish-html" in text |
| 34 | assert "--publish" in flags |
| 35 | assert "library feed --publish" in text |
| 36 | assert "--publish-password" in flags |
| 37 | assert "--publish-password" in text |
| 38 | assert "LAST30DAYS_PUBLISH_PASSWORD" in text |
| 39 | assert "--record-fixtures" in flags |
| 40 | assert "--record-fixtures" in text |
| 41 | assert "docs/reference/eval.md" in text |
| 42 | |
| 43 | |
| 44 | def test_html_publish_reference_prompts_for_password_choice(): |
| 45 | text = HTML_REFERENCE.read_text(encoding="utf-8") |
| 46 | publish_section = text[text.index("## Optional hosted publishing"):text.index("## What ends up in the HTML file")] |
| 47 | assert "Respect any existing user, project, or host preference for HTML publishing first" in publish_section |
| 48 | assert "If multiple publishing options are available, show each as its own choice" in publish_section |
| 49 | assert "label `ht-ml.app` as supporting optional password protection" in publish_section |
| 50 | assert "Show the absolute saved path" in publish_section |
| 51 | assert "Open HTML file" in publish_section |
| 52 | assert "Done for now" in publish_section |
| 53 | assert "Do not upload until the user chooses a publishing option" in publish_section |
| 54 | assert "ask a second question" in publish_section |
| 55 | assert "**Public link** - publish without a password" in publish_section |
| 56 | assert "**Password-protected link** - ask the user to type the shared password" in publish_section |
| 57 | assert "repeat the shared password they selected" in publish_section |
| 58 | assert "LAST30DAYS_PUBLISH_PASSWORD" in publish_section |
| 59 | assert '--output "$HTML_PATH"' in publish_section |
| 60 | assert "<HTML_PATH>.publish.json" in publish_section |
| 61 | |
| 62 | |
| 63 | def test_reddit_backend_env_var_is_documented_for_users_and_runtime_skill(): |
| 64 | config_text = CONFIGURATION.read_text(encoding="utf-8") |
| 65 | skill_text = SKILL_MD.read_text(encoding="utf-8") |
| 66 | |
| 67 | assert "LAST30DAYS_REDDIT_BACKEND=scrapecreators" in config_text |
| 68 | assert "LAST30DAYS_REDDIT_BACKEND=scrapecreators" in skill_text |
| 69 | # Thinness floor shipped with the keyless Reddit path; keep user docs honest. |
| 70 | assert "LAST30DAYS_REDDIT_SC_MIN_ITEMS" in config_text |
| 71 | assert "LAST30DAYS_REDDIT_SC_MIN_ITEMS" in skill_text |
| 72 | # Security copy must not claim transport/rate-limit escalation for Reddit search. |
| 73 | assert "when public Reddit is unavailable" not in skill_text |
| 74 | assert "backup when the free path returns no items" in skill_text or ( |
| 75 | "returns **no items**" in skill_text |
| 76 | ) |
| 77 | |
| 78 | |
| 79 | def test_save_is_not_documented_as_python_cli_flag(): |
| 80 | text = CONFIGURATION.read_text(encoding="utf-8") |
| 81 | assert "--save-dir <path>" in text |
| 82 | assert "--save " not in text |
| 83 | assert "`--save`" not in text |
| 84 | |
| 85 | |
| 86 | def test_agent_is_documented_as_skill_argument_not_python_flag(): |
| 87 | text = SKILL_MD.read_text(encoding="utf-8") |
| 88 | start = text.index("## Agent Mode (--agent flag)") |
| 89 | agent_section = text[start:start + 2000] |
| 90 | assert "If `--agent` appears in ARGUMENTS" in agent_section |
| 91 | assert "slash-command skill contract" in text |
| 92 | assert "not a Python CLI flag" in text |
| 93 | |
| 94 | |
| 95 | def test_html_reference_documents_structured_cache_reuse(): |
| 96 | text = HTML_REFERENCE.read_text(encoding="utf-8") |
| 97 | assert "~/.config/last30days/last-report.json" in text |
| 98 | assert "without re-running source fetchers" in text |
| 99 | assert "No matching cached report data" in text |
| 100 | assert "LAST30DAYS_REPORT_CACHE_TTL_SECONDS" in text |
| 101 | assert "default: one hour" in text |
| 102 | |
| 103 | |
| 104 | def test_configuration_documents_report_cache_ttl(): |
| 105 | text = CONFIGURATION.read_text(encoding="utf-8") |
| 106 | assert "LAST30DAYS_REPORT_CACHE_TTL_SECONDS" in text |
| 107 | assert "defaults to `3600`" in text |
| 108 | assert "`0` to disable report-cache reuse" in text |
| 109 | |
| 110 | |
| 111 | def test_comparison_artifact_contract_documents_actual_paths(): |
| 112 | text = SKILL_MD.read_text(encoding="utf-8") |
| 113 | comparison_start = text.index("\n## If QUERY_TYPE = COMPARISON\n") |
| 114 | comparison_section = text[comparison_start:comparison_start + 5000] |
| 115 | assert "there is no separate merged Markdown raw file" in comparison_section |
| 116 | assert "[last30days] Comparison artifact set: main={path}; peers={path, ...}" in comparison_section |
| 117 | assert "Treat that log line as authoritative" in comparison_section |
| 118 | |
| 119 | step_start = text.index("## Step 2.5: Append WebSearch Results to Saved Raw File") |
| 120 | step_section = text[step_start:step_start + 3500] |
| 121 | assert "append the same `## WebSearch Supplemental Results` section to every listed per-entity Markdown raw file" in step_section |
| 122 | assert "do not append Markdown text to `.html` or `.json`" in step_section |
| 123 |