from __future__ import annotations import tempfile from pathlib import Path import unittest import warnings import httpx warnings.filterwarnings("ignore", category=DeprecationWarning) from model_selector.config import provider_label from model_selector.main import create_app from model_selector.providers import ProviderModel from model_selector.settings import AppSettings class FakeAdapter: def __init__(self, provider_key: str, models: list[dict]) -> None: self.provider_key = provider_key self.models = models async def fetch(self): return [ ProviderModel( provider_key=self.provider_key, provider_label=provider_label(self.provider_key), model_id=item["id"], name=item.get("name", item["id"]), source="live", reasoning=item.get("reasoning"), context_window=item.get("context_window"), max_tokens=item.get("max_tokens"), pricing_prompt=item.get("pricing_prompt"), pricing_completion=item.get("pricing_completion"), pricing_request=item.get("pricing_request"), input_modalities=tuple(item.get("input_modalities", ("text",))), output_modalities=tuple(item.get("output_modalities", ("text",))), supported_parameters=tuple(item.get("supported_parameters", ())), active=item.get("active"), features=tuple(item.get("features", ())), raw=item, ) for item in self.models ] class AppTests(unittest.IsolatedAsyncioTestCase): async def test_dashboard_renders_expected_panels(self) -> None: with tempfile.TemporaryDirectory() as tmp: config_path = Path(tmp) / "openclaw.json" config_path.write_text( """ { "models": { "providers": { "openai": {"models": [{"id": "gpt-5.4", "name": "GPT-5.4", "reasoning": true, "input": ["text"], "contextWindow": 100, "maxTokens": 10}]}, "groq-cloud": {"models": [{"id": "openai/gpt-oss-20b", "name": "GPT OSS 20B", "reasoning": true, "input": ["text"], "contextWindow": 100, "maxTokens": 10}]}, "openrouter": {"models": [{"id": "openai/gpt-oss-120b", "name": "GPT OSS 120B", "reasoning": true, "input": ["text"], "contextWindow": 100, "maxTokens": 10}]} } }, "agents": {"defaults": {"models": {"openai/gpt-5.4": {"alias": "osiris"}}}} } """.strip(), encoding="utf-8", ) app = create_app( settings=AppSettings( config_path=config_path, cache_dir=Path(tmp) / "cache", refresh_interval_seconds=3600, host="127.0.0.1", port=3300, ), adapters={ "openai": FakeAdapter("openai", [{"id": "gpt-5.4", "name": "GPT-5.4", "reasoning": True}]), "groq-cloud": FakeAdapter("groq-cloud", [{"id": "openai/gpt-oss-20b", "name": "GPT OSS 20B", "reasoning": True}]), "openrouter": FakeAdapter("openrouter", [{"id": "openai/gpt-oss-120b", "name": "GPT OSS 120B"}]), }, ) await app.state.service.refresh_all() transport = httpx.ASGITransport(app=app) async with httpx.AsyncClient(transport=transport, base_url="http://test") as client: response = await client.get("/") add_response = await client.post( "/api/config/models/add", json={"provider_key": "openrouter", "model_id": "openai/gpt-oss-120b"}, ) delete_response = await client.post( "/api/config/models/delete", json={"full_key": "openrouter/openai/gpt-oss-120b"}, ) self.assertEqual(response.status_code, 200) self.assertEqual(add_response.status_code, 200) self.assertEqual(delete_response.status_code, 200) self.assertTrue( any( model["id"] == "openai/gpt-oss-120b" for model in add_response.json()["config"]["models"]["providers"]["openrouter"]["models"] ) ) self.assertFalse( any( model["id"] == "openai/gpt-oss-120b" for model in delete_response.json()["config"]["models"]["providers"]["openrouter"]["models"] ) ) html = response.text self.assertIn("Configured Models", html) self.assertIn("Provider Catalog", html) self.assertIn("OpenAI", html) self.assertIn("Groq", html) self.assertIn("OpenRouter", html)