← Files FactIQARCHIVED FILE

tests/test_directory_submission_readiness.py

6.76 KB · Nov 2, 2026 · 12:30 UTC

↓ Download file

See the change to this file →

import json
import struct
import unittest
from pathlib import Path


ROOT = Path(__file__).resolve().parents[1]


class DirectorySubmissionReadinessTests(unittest.TestCase):
    def test_readme_names_all_supported_clients_and_canonical_guides(self):
        readme = (ROOT / "README.md").read_text(encoding="utf-8")

        for expected in (
            "Claude (web, desktop, and Cowork)",
            "ChatGPT Desktop",
            "https://www.factiq.com/claude",
            "https://www.factiq.com/chatgpt",
            "https://www.factiq.com/claude-code",
            "https://www.factiq.com/codex",
        ):
            self.assertIn(expected, readme)

    def test_manifests_have_complete_factiq_publisher_metadata(self):
        plugin = json.loads(
            (ROOT / ".claude-plugin" / "plugin.json").read_text(encoding="utf-8")
        )
        codex = json.loads(
            (ROOT / ".codex-plugin" / "plugin.json").read_text(encoding="utf-8")
        )
        marketplace = json.loads(
            (ROOT / ".claude-plugin" / "marketplace.json").read_text(
                encoding="utf-8"
            )
        )

        expected_publisher = {
            "name": "FactIQ",
            "url": "https://www.factiq.com",
        }
        self.assertEqual(plugin["author"], expected_publisher)
        self.assertEqual(codex["author"], expected_publisher)
        self.assertEqual(codex["interface"]["developerName"], "FactIQ")
        self.assertEqual(
            codex["interface"]["websiteURL"], "https://www.factiq.com"
        )
        self.assertEqual(
            codex["interface"]["privacyPolicyURL"],
            "https://www.factiq.com/privacy",
        )
        self.assertEqual(
            codex["interface"]["termsOfServiceURL"],
            "https://www.factiq.com/legal",
        )
        self.assertEqual(
            plugin["repository"], "https://github.com/defog-ai/factiq-plugin"
        )
        self.assertEqual(plugin["license"], "MIT")
        self.assertTrue(plugin["keywords"])
        self.assertTrue(marketplace["description"])
        self.assertEqual(marketplace["owner"], expected_publisher)

    def test_claude_and_codex_versions_stay_in_sync(self):
        claude = json.loads(
            (ROOT / ".claude-plugin" / "plugin.json").read_text(encoding="utf-8")
        )
        codex = json.loads(
            (ROOT / ".codex-plugin" / "plugin.json").read_text(encoding="utf-8")
        )
        self.assertEqual(claude["version"], codex["version"])

    def test_codex_manifest_links_the_registered_chatgpt_app(self):
        codex = json.loads(
            (ROOT / ".codex-plugin" / "plugin.json").read_text(encoding="utf-8")
        )
        self.assertEqual(codex["apps"], "./.app.json")
        self.assertEqual(codex["mcpServers"], "./.mcp.json")

        apps = json.loads((ROOT / ".app.json").read_text(encoding="utf-8"))
        self.assertEqual(list(apps["apps"]), ["factiq"])
        self.assertRegex(
            apps["apps"]["factiq"]["id"], r"^asdk_app_[0-9a-f]{32}$"
        )

    def test_codex_listing_copy_and_assets_fit_directory_constraints(self):
        manifest = json.loads(
            (ROOT / ".codex-plugin" / "plugin.json").read_text(encoding="utf-8")
        )
        interface = manifest["interface"]

        expected_long_description = (
            "FactIQ gives ChatGPT and Codex primarily read-only access to sourced "
            "investment and economic data through one normalized warehouse. "
            "Research official statistics across the US, UK, EU, China, India, "
            "Singapore, and international institutions; inspect company filings "
            "and earnings-call intelligence; query market prices, business news, "
            "executive media appearances, trade, and satellite-derived signals "
            "such as rainfall, fires, air quality, nighttime lights, shipping, and "
            "reservoir levels. FactIQ can discover datasets, run read-only SQL, "
            "calculate comparisons, return sourced analysis, and create inline "
            "charts. A free FactIQ account is enough to connect. FactIQ records "
            "connector tool inputs and outputs for reliability and support as "
            "described in the privacy policy. `send_feedback` is the only write "
            "tool and sends a short issue report to the FactIQ team; it does not "
            "modify research data."
        )
        expected_prompts = [
            "Compare US inflation and unemployment since 2020 and explain the "
            "turning points.",
            "What did Microsoft say about AI spending in its latest earnings call, "
            "and how does that compare with filed results?",
            "Compare Punjab rainfall and fire activity during the 2024 and 2025 "
            "monsoon seasons.",
        ]

        self.assertEqual(
            interface["shortDescription"], "Investment and economic data"
        )
        self.assertEqual(interface["longDescription"], expected_long_description)
        self.assertEqual(interface["defaultPrompt"], expected_prompts)
        self.assertLessEqual(len(interface["shortDescription"]), 30)
        self.assertLessEqual(len(interface["defaultPrompt"]), 3)
        for prompt in interface["defaultPrompt"]:
            self.assertLessEqual(len(prompt), 128)

        for field in ("composerIcon", "logo"):
            relative_path = interface[field]
            self.assertTrue(relative_path.startswith("./assets/"))
            asset = ROOT / relative_path.removeprefix("./")
            self.assertTrue(asset.is_file())
            self.assertLessEqual(asset.stat().st_size, 5 * 1024 * 1024)
            png = asset.read_bytes()
            self.assertEqual(png[:8], b"\x89PNG\r\n\x1a\n")
            width, height = struct.unpack(">II", png[16:24])
            self.assertEqual(width, height)
            self.assertGreaterEqual(width, 48)
            self.assertLessEqual(width, 4096)

    def test_skill_uses_current_market_contract_without_broad_permissions(self):
        skill = (ROOT / "skills" / "factiq" / "SKILL.md").read_text(
            encoding="utf-8"
        )
        self.assertNotIn("allowed-tools:", skill)
        self.assertIn(
            "`get_market_data` (`asset`, `data_type?`, `frequency?`, `limit?`)",
            skill,
        )
        for legacy_parameter in ("`function`", "`outputsize?`"):
            self.assertNotIn(legacy_parameter, skill)

    def test_setup_covers_terminal_and_terminal_free_auth(self):
        setup = (ROOT / "SETUP.md").read_text(encoding="utf-8")
        for expected in (
            "Claude Code",
            "/mcp",
            "Cowork has no terminal",
            "Customize → Plugins",
            "get_data_catalog",
            "Do not ask",
        ):
            self.assertIn(expected, setup)


if __name__ == "__main__":
    unittest.main()

SHA-256: 996daa950b56bd3368d7e45221c75b8fa9ce7a1a7a23d91734f8ddc286f81f3a