Some checks failed
Release / Bump version and tag (push) Successful in 4s
Sidecar Release / Bump sidecar version and tag (push) Failing after 3s
Tests / Python Backend Tests (push) Failing after 3s
Tests / Frontend Tests (push) Successful in 8s
Tests / Rust Sidecar Tests (push) Successful in 3m10s
Test suite covering all three layers: Python backend (25 tests): - AppController: state machine, start/stop, callbacks, settings reload - API server: REST endpoints, config CRUD, status, devices - Config: dot-notation get/set, persistence, nested paths - Main headless: ready event port format validation Svelte frontend (14 tests via Vitest): - Backend store: exported properties/methods, port derivation, URLs - Config store: method names (fetchConfig not loadConfig), defaults - Transcriptions store: add/clear/plaintext - File extension regression: ensures $state runes only in .svelte.ts Rust sidecar (24 tests via cargo test): - Platform/arch detection, asset name construction - Ready event deserialization (with extra fields tolerance) - Path construction, version read/write, old version cleanup - Zip extraction, SidecarManager lifecycle CI workflow (.gitea/workflows/test.yml): - Runs on push to main and PRs - Three parallel jobs: Python, Frontend, Rust Also fixes three bugs found during test planning: - Settings: /api/check-updates -> GET /api/check-update - Settings: /api/remote/login -> /api/login - Settings: /api/remote/register -> /api/register Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
151 lines
5.4 KiB
Python
151 lines
5.4 KiB
Python
"""Tests for backend.api_server.APIServer REST endpoints."""
|
|
|
|
import sys
|
|
from pathlib import Path
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
import pytest
|
|
import pytest_asyncio
|
|
|
|
# Ensure project root is on path
|
|
project_root = Path(__file__).resolve().parent.parent.parent
|
|
sys.path.insert(0, str(project_root))
|
|
|
|
|
|
# ── GET /api/status ─────────────────────────────────────────────────
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_status(api_client):
|
|
resp = await api_client.get("/api/status")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert "state" in data
|
|
assert "is_transcribing" in data
|
|
assert "version" in data
|
|
assert "web_server" in data
|
|
|
|
|
|
# ── GET /api/config ─────────────────────────────────────────────────
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_config(api_client):
|
|
resp = await api_client.get("/api/config")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
# The config should be a dict (the raw config mapping)
|
|
assert isinstance(data, dict)
|
|
|
|
|
|
# ── PUT /api/config ─────────────────────────────────────────────────
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_put_config(api_client, controller):
|
|
"""Updating config via PUT should persist and return success."""
|
|
# Patch reload_engine to avoid heavy lifting
|
|
controller.reload_engine = MagicMock(return_value=(True, "ok"))
|
|
controller.current_model_size = "base.en"
|
|
controller.current_device_config = "auto"
|
|
controller.config.set("transcription.model", "base.en")
|
|
controller.config.set("transcription.device", "auto")
|
|
|
|
resp = await api_client.put(
|
|
"/api/config",
|
|
json={"settings": {"display.font_size": 24}},
|
|
)
|
|
assert resp.status_code == 200
|
|
body = resp.json()
|
|
assert body["status"] == "ok"
|
|
|
|
# Verify the value was actually saved
|
|
assert controller.config.get("display.font_size") == 24
|
|
|
|
|
|
# ── POST /api/start (engine not ready) ─────────────────────────────
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_start_when_not_ready(api_client, controller):
|
|
"""Starting transcription without an engine should return 400."""
|
|
controller.transcription_engine = None
|
|
resp = await api_client.post("/api/start")
|
|
assert resp.status_code == 400
|
|
|
|
|
|
# ── POST /api/clear ─────────────────────────────────────────────────
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_clear(api_client, controller):
|
|
from client.transcription_engine_realtime import TranscriptionResult
|
|
from datetime import datetime
|
|
|
|
controller.transcriptions = [
|
|
TranscriptionResult(text="One", is_final=True, timestamp=datetime.now(), user_name="U"),
|
|
]
|
|
|
|
resp = await api_client.post("/api/clear")
|
|
assert resp.status_code == 200
|
|
body = resp.json()
|
|
assert body["cleared"] == 1
|
|
assert len(controller.transcriptions) == 0
|
|
|
|
|
|
# ── GET /api/audio-devices ──────────────────────────────────────────
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_audio_devices(api_client, controller):
|
|
"""Audio devices endpoint should return a list, even when mocked."""
|
|
# Mock sounddevice so the test works without audio hardware
|
|
with patch("backend.app_controller.AppController.get_audio_devices",
|
|
return_value=[{"index": 0, "name": "Mock Mic"}]):
|
|
resp = await api_client.get("/api/audio-devices")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert "devices" in data
|
|
assert len(data["devices"]) >= 1
|
|
|
|
|
|
# ── GET /api/compute-devices ────────────────────────────────────────
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_compute_devices(api_client, controller):
|
|
resp = await api_client.get("/api/compute-devices")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert "devices" in data
|
|
# At minimum we get the "Auto-detect" entry
|
|
assert any(d["id"] == "auto" for d in data["devices"])
|
|
|
|
|
|
# ── GET /api/check-update ──────────────────────────────────────────
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_check_update(api_client, controller):
|
|
"""check-update should return a dict with an 'available' key."""
|
|
with patch.object(controller, "check_for_updates",
|
|
return_value={"available": False, "current_version": "1.0.0"}):
|
|
resp = await api_client.get("/api/check-update")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert "available" in data
|
|
|
|
|
|
# ── GET /api/version ────────────────────────────────────────────────
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_version(api_client):
|
|
resp = await api_client.get("/api/version")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert "version" in data
|
|
# Should be a non-empty string
|
|
assert isinstance(data["version"], str)
|
|
assert len(data["version"]) > 0
|