From a0af15a037e84b9398162f57a494e79223dae3c3 Mon Sep 17 00:00:00 2001 From: Dystroyer8 Date: Thu, 30 Jul 2026 18:11:27 +0200 Subject: [PATCH] test: cover remaining cloud fallback safeguards --- .gitignore | 1 + docs/CHATGPT_HANDOFF.md | 15 ++++++++++----- docs/PROJECT_STATUS.md | 8 ++++---- tests/unit/test_gemini_provider.py | 8 ++++++++ tests/unit/test_provider_router.py | 16 ++++++++++++++++ 5 files changed, 39 insertions(+), 9 deletions(-) diff --git a/.gitignore b/.gitignore index 956b6fc..bfe4884 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ key-pw.xml secrets.local.xml *.local.xml config/javis.toml +/javis.toml config/*secret*.xml !config/*.example.xml diff --git a/docs/CHATGPT_HANDOFF.md b/docs/CHATGPT_HANDOFF.md index 10eb120..f6d915d 100644 --- a/docs/CHATGPT_HANDOFF.md +++ b/docs/CHATGPT_HANDOFF.md @@ -170,10 +170,15 @@ Letzter bestätigter Projektstand: - uv-Lock und `uv sync --dev`: bestanden - Python in `.venv`: 3.12.13 - Ruff in `.venv`: 0.16.0 -- Unit-Tests: 60 bestanden; Startskript-Sicherheitsregeln zusätzlich abgedeckt +- Unit-Tests: 62 bestanden; Timeout und fehlendes Modell fallen nachweislich lokal zurück - PowerShell-Syntax des Startskripts: erfolgreich geparst - Ruff Lint: bestanden - Ruff Formatprüfung: bestanden +- lokaler nachrichtenfreier CLI-Smoke: bestanden; Nullkostenmodus aktiv, + Gemini nicht konfiguriert, Schlüssel nicht vorhanden, 0 Cloudanfragen +- Obsidian-Vault unverändert: 1 Markdown-, 11 Gesamtdateien, letzter + Schreibzeitpunkt weiterhin 30.07.2026 15:42:31 +- Rootserver und Ubuntu-Laptop wurden nicht kontaktiert Lokaler Modell-Smoke-Test: @@ -257,10 +262,10 @@ Abnahmestatus: Auf dem bestehenden Feature-Branch als nächstes einzelnes Arbeitspaket: -1. geforderte Resttests ergänzen, insbesondere Timeout und bereinigte Fehler -2. vollständige Qualitäts-, Gitignore-, Secret- und Außenbereichsprüfung ausführen -3. Abschlussdokumentation aktualisieren, Feature-Branch pushen und vor Pascals - manuellem Live-Test stoppen; nicht nach `main` mergen +1. veraltete Architektur-, Sicherheits-, Changelog- und Nächste-Sitzung-Texte + auf den tatsächlich implementierten Stand bringen +2. Feature-Branch pushen und vor Pascals manuellem Live-Test stoppen +3. nicht nach `main` mergen Keinen API-Key in Chat, Git, Obsidian, Logs oder Dokumentation übernehmen. diff --git a/docs/PROJECT_STATUS.md b/docs/PROJECT_STATUS.md index 4600f87..a022029 100644 --- a/docs/PROJECT_STATUS.md +++ b/docs/PROJECT_STATUS.md @@ -60,7 +60,8 @@ Stand: lokaler Textchat auf `main`, 30.07.2026. - `/provider`, `/privacy` und `/status` geben keine Inhalte oder Keywerte aus - sicheres `scripts/start-javis.ps1` mit relativer Repositoryprüfung, Loopback-Ollama und überschreibbaren externen Pfaden -- 60 Unit-Tests aktuell erfolgreich; PowerShell-Syntax, Ruff-Format und Lint sauber +- 62 Unit-Tests aktuell erfolgreich; PowerShell-Syntax, Lockfile, Sync, + Strukturprüfung, Secret-Scan, Gitignore, Ruff-Format und Lint sauber ## Nicht implementiert @@ -85,6 +86,5 @@ Living-Mind-Frontend und automatische Ollama-Prozessverwaltung. Ollama wird bewusst manuell in einem eigenen Terminal gestartet. Antworten werden noch nicht gestreamt; Sitzungen haben noch keine Titel, Suche oder Löschfunktion. `qwen3:8b` ist nicht automatisch für die GTX 1050 des späteren Zielhosts geeignet. -Abschließende Resttests, Qualitätsprüfung und Feature-Branch-Push fehlen noch. -Es wurde kein echter API-Key angefordert, angezeigt oder konfiguriert und keine -Cloudanfrage ausgeführt. +Nur Abschlussdokumentation und Feature-Branch-Push fehlen. Es wurde kein echter +API-Key angefordert, angezeigt oder konfiguriert und keine Cloudanfrage ausgeführt. diff --git a/tests/unit/test_gemini_provider.py b/tests/unit/test_gemini_provider.py index 0a7984b..091a76d 100644 --- a/tests/unit/test_gemini_provider.py +++ b/tests/unit/test_gemini_provider.py @@ -14,6 +14,7 @@ from javis.providers.base import ( InvalidProviderResponseError, MissingApiKeyError, ProviderRateLimitError, + ProviderTimeoutError, ) from javis.providers.gemini import GeminiProvider @@ -140,6 +141,13 @@ class GeminiProviderTests(unittest.TestCase): with self.assertRaises(CloudNetworkError): provider.chat([ChatMessage("user", "Was ist Python?")]) + def test_timeout_is_a_distinct_fallback_signal(self) -> None: + request = httpx.Request("POST", "https://example.invalid") + provider, _ = self._provider(httpx.ReadTimeout("timeout", request=request)) + + with self.assertRaises(ProviderTimeoutError): + provider.chat([ChatMessage("user", "Was ist Python?")]) + def test_empty_response_is_rejected(self) -> None: provider, _ = self._provider(_response(" ")) diff --git a/tests/unit/test_provider_router.py b/tests/unit/test_provider_router.py index fba8d47..2fa8a93 100644 --- a/tests/unit/test_provider_router.py +++ b/tests/unit/test_provider_router.py @@ -8,11 +8,13 @@ from javis.core.provider_router import HybridProvider from javis.memory.usage_store import ProviderEvent, SQLiteUsageStore from javis.providers.base import ( ChatMessage, + CloudModelUnavailableError, CloudNetworkError, InvalidApiKeyError, LocalModelProvider, MissingApiKeyError, ProviderRateLimitError, + ProviderTimeoutError, ProviderUsage, ) from javis.security.privacy import CloudPolicy, PrivacyRouter @@ -167,6 +169,20 @@ class HybridProviderTests(unittest.TestCase): self.assertEqual(answer, "Lokal") self.assertTrue(router.last_route.fallback) + def test_timeout_and_missing_model_fall_back(self) -> None: + for error in ( + ProviderTimeoutError("timeout"), + CloudModelUnavailableError("missing model"), + ): + with self.subTest(error=type(error).__name__): + self.cloud.error = error + router = self._router() + + answer = router.chat([ChatMessage("user", "Wie funktioniert Python?")]) + + self.assertEqual(answer, "Lokal") + self.assertTrue(router.last_route.fallback) + def test_missing_key_falls_back(self) -> None: def missing_key() -> _RecordingProvider: raise MissingApiKeyError("missing")