test_app.py 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. from __future__ import annotations
  2. import tempfile
  3. from pathlib import Path
  4. import unittest
  5. import warnings
  6. import httpx
  7. warnings.filterwarnings("ignore", category=DeprecationWarning)
  8. from model_selector.config import provider_label
  9. from model_selector.main import create_app
  10. from model_selector.providers import ProviderModel
  11. from model_selector.settings import AppSettings
  12. class FakeAdapter:
  13. def __init__(self, provider_key: str, models: list[dict]) -> None:
  14. self.provider_key = provider_key
  15. self.models = models
  16. async def fetch(self):
  17. return [
  18. ProviderModel(
  19. provider_key=self.provider_key,
  20. provider_label=provider_label(self.provider_key),
  21. model_id=item["id"],
  22. name=item.get("name", item["id"]),
  23. source="live",
  24. reasoning=item.get("reasoning"),
  25. context_window=item.get("context_window"),
  26. max_tokens=item.get("max_tokens"),
  27. pricing_prompt=item.get("pricing_prompt"),
  28. pricing_completion=item.get("pricing_completion"),
  29. pricing_request=item.get("pricing_request"),
  30. input_modalities=tuple(item.get("input_modalities", ("text",))),
  31. output_modalities=tuple(item.get("output_modalities", ("text",))),
  32. supported_parameters=tuple(item.get("supported_parameters", ())),
  33. active=item.get("active"),
  34. features=tuple(item.get("features", ())),
  35. raw=item,
  36. )
  37. for item in self.models
  38. ]
  39. class AppTests(unittest.IsolatedAsyncioTestCase):
  40. async def test_dashboard_renders_expected_panels(self) -> None:
  41. with tempfile.TemporaryDirectory() as tmp:
  42. config_path = Path(tmp) / "openclaw.json"
  43. config_path.write_text(
  44. """
  45. {
  46. "models": {
  47. "providers": {
  48. "openai": {"models": [{"id": "gpt-5.4", "name": "GPT-5.4", "reasoning": true, "input": ["text"], "contextWindow": 100, "maxTokens": 10}]},
  49. "groq-cloud": {"models": [{"id": "openai/gpt-oss-20b", "name": "GPT OSS 20B", "reasoning": true, "input": ["text"], "contextWindow": 100, "maxTokens": 10}]},
  50. "openrouter": {"models": [{"id": "openai/gpt-oss-120b", "name": "GPT OSS 120B", "reasoning": true, "input": ["text"], "contextWindow": 100, "maxTokens": 10}]}
  51. }
  52. },
  53. "agents": {"defaults": {"models": {"openai/gpt-5.4": {"alias": "osiris"}}}}
  54. }
  55. """.strip(),
  56. encoding="utf-8",
  57. )
  58. app = create_app(
  59. settings=AppSettings(
  60. config_path=config_path,
  61. cache_dir=Path(tmp) / "cache",
  62. refresh_interval_seconds=3600,
  63. host="127.0.0.1",
  64. port=3300,
  65. ),
  66. adapters={
  67. "openai": FakeAdapter("openai", [{"id": "gpt-5.4", "name": "GPT-5.4", "reasoning": True}]),
  68. "groq-cloud": FakeAdapter("groq-cloud", [{"id": "openai/gpt-oss-20b", "name": "GPT OSS 20B", "reasoning": True}]),
  69. "openrouter": FakeAdapter("openrouter", [{"id": "openai/gpt-oss-120b", "name": "GPT OSS 120B"}]),
  70. },
  71. )
  72. await app.state.service.refresh_all()
  73. transport = httpx.ASGITransport(app=app)
  74. async with httpx.AsyncClient(transport=transport, base_url="http://test") as client:
  75. response = await client.get("/")
  76. add_response = await client.post(
  77. "/api/config/models/add",
  78. json={"provider_key": "openrouter", "model_id": "openai/gpt-oss-120b"},
  79. )
  80. delete_response = await client.post(
  81. "/api/config/models/delete",
  82. json={"full_key": "openrouter/openai/gpt-oss-120b"},
  83. )
  84. self.assertEqual(response.status_code, 200)
  85. self.assertEqual(add_response.status_code, 200)
  86. self.assertEqual(delete_response.status_code, 200)
  87. self.assertTrue(
  88. any(
  89. model["id"] == "openai/gpt-oss-120b"
  90. for model in add_response.json()["config"]["models"]["providers"]["openrouter"]["models"]
  91. )
  92. )
  93. self.assertFalse(
  94. any(
  95. model["id"] == "openai/gpt-oss-120b"
  96. for model in delete_response.json()["config"]["models"]["providers"]["openrouter"]["models"]
  97. )
  98. )
  99. html = response.text
  100. self.assertIn("Configured Models", html)
  101. self.assertIn("Provider Catalog", html)
  102. self.assertIn("OpenAI", html)
  103. self.assertIn("Groq", html)
  104. self.assertIn("OpenRouter", html)