"""Tests for the standalone Maris human training Space app.""" from __future__ import annotations import importlib import json import sys import tempfile from pathlib import Path from fastapi.testclient import TestClient REPO_ROOT = Path(__file__).resolve().parents[2] if str(REPO_ROOT) not in sys.path: sys.path.insert(0, str(REPO_ROOT)) human_training_space_app = importlib.import_module("huggingface_human_training_space.app") def test_index_contains_private_space_entry_and_role_sections() -> None: client = TestClient(human_training_space_app.app) response = client.get("/") assert response.status_code == 200 assert "Maris AI Human Training" in response.text assert "Maris AI logo" in response.text assert "Private Space" in response.text assert "Workspace ir atvērts uzreiz" in response.text assert 'id="login-form"' not in response.text assert 'id="register-form"' not in response.text assert 'id="register-role"' not in response.text assert "owner" in response.text assert "secretary" in response.text assert "trainee" in response.text assert "Platformas dokumentācija" in response.text assert 'class="section active" id="workspace-shell"' in response.text assert 'id="human_hub_model_id"' in response.text assert 'id="human_continue_from_latest_artefact"' in response.text assert 'id="human-template-select"' in response.text assert 'id="human-publish-button"' in response.text assert "Ātra starta vadība" in response.text assert "Staging pārskats" in response.text def test_runtime_endpoint_exposes_roles_and_docs() -> None: client = TestClient(human_training_space_app.app) response = client.get("/api/runtime") assert response.status_code == 200 body = response.json() assert body["service"] == "maris-human-training-space" assert set(body["roles"]) == {"owner", "secretary", "trainee", "user"} assert any(item["title"] == "Onboarding guide" for item in body["documentation"]) assert "customer-support-lv" in body["templates"] assert body["templates"]["customer-support-lv"]["payload"]["profile_facts"] assert body["training"]["defaults"]["hub_model_id"] == "MarisUK/maris-ai-text" assert isinstance(body["training"]["model_choices"], list) assert body["training"]["model_choices"][0]["id"] assert body["training"]["model_choices"][0]["label"] assert body["auth_required"] is False assert body["private_session"]["user"]["role"] == "owner" def test_maybe_start_automatic_training_starts_with_human_space_defaults( monkeypatch, tmp_path: Path ) -> None: calls: list[dict[str, object]] = [] monkeypatch.setenv("MARIS_HUMAN_TRAINING_AUTO_TRAIN", "true") monkeypatch.setattr(human_training_space_app, "PERSISTENT_DIR", tmp_path) monkeypatch.setattr( human_training_space_app, "has_completed_training_artifacts", lambda output_dir: False, ) monkeypatch.setattr( human_training_space_app, "_start_training_process", lambda request: ( calls.append(request.model_dump()) or {"pid": 77, "log_path": "/tmp/human-train.log"} ), ) human_training_space_app._maybe_start_automatic_training() assert len(calls) == 1 assert calls[0]["dataset_repo"] == human_training_space_app.DEFAULT_DATASET_REPO assert calls[0]["model_repo"] == human_training_space_app.DEFAULT_HUB_MODEL_ID assert calls[0]["model_preset"] == "balanced" assert calls[0]["continue_from_latest_artifact"] is True def test_register_and_login_flow(monkeypatch, tmp_path: Path) -> None: client = TestClient(human_training_space_app.app) monkeypatch.setattr( human_training_space_app, "USERS_FILE", tmp_path / "human-training-users.json", ) human_training_space_app.SESSION_STORE.clear() register_response = client.post( "/api/auth/register", json={ "full_name": "Māris Ozols", "email": "maris@example.com", "password": "drosha-parole-123", "role": "owner", }, ) assert register_response.status_code == 200 register_body = register_response.json() assert register_body["user"]["role"] == "owner" assert register_body["role_guide"]["label"] == "Owner" assert register_body["token"] login_response = client.post( "/api/auth/login", json={ "email": "maris@example.com", "password": "drosha-parole-123", }, ) assert login_response.status_code == 200 login_body = login_response.json() assert login_body["user"]["email"] == "maris@example.com" assert login_body["platform"]["examples"] def test_register_with_storage_fallback(monkeypatch) -> None: client = TestClient(human_training_space_app.app) original_users_file = human_training_space_app.USERS_FILE fallback_root = ( Path(tempfile.gettempdir()) / human_training_space_app.USER_STORE_FALLBACK_DIRNAME ) fallback_file = fallback_root / original_users_file.name if fallback_file.exists(): fallback_file.unlink() original_path_mkdir = Path.mkdir def fail_default_data_mkdir(self: Path, *args, **kwargs) -> None: if self == original_users_file.parent: raise PermissionError("read-only") return original_path_mkdir(self, *args, **kwargs) monkeypatch.setattr(human_training_space_app, "USERS_FILE", original_users_file) monkeypatch.setattr(Path, "mkdir", fail_default_data_mkdir) human_training_space_app.SESSION_STORE.clear() response = client.post( "/api/auth/register", json={ "full_name": "Māris Ozols", "email": "maris-fallback@example.com", "password": "drosha-parole-123", "role": "owner", }, ) assert response.status_code == 200 assert fallback_file.exists() payload = json.loads(fallback_file.read_text(encoding="utf-8")) assert payload["maris-fallback@example.com"]["role"] == "owner" def test_login_rejects_invalid_password(monkeypatch, tmp_path: Path) -> None: client = TestClient(human_training_space_app.app) monkeypatch.setattr( human_training_space_app, "USERS_FILE", tmp_path / "human-training-users.json", ) human_training_space_app.SESSION_STORE.clear() client.post( "/api/auth/register", json={ "full_name": "Māris Ozols", "email": "maris@example.com", "password": "drosha-parole-123", "role": "owner", }, ) response = client.post( "/api/auth/login", json={ "email": "maris@example.com", "password": "nepareiza-parole", }, ) assert response.status_code == 401 assert response.json()["detail"] == "Nepareizs e-pasts vai parole." def test_human_training_build_allows_private_space_without_session( monkeypatch, tmp_path: Path ) -> None: client = TestClient(human_training_space_app.app) monkeypatch.setattr(human_training_space_app, "PERSISTENT_DIR", tmp_path) response = client.post( "/api/human-training/build", json={ "dataset_repo": "example-user/memory-dataset", "hub_model_id": "example-user/custom-model", "profile_facts": ["Man vajag profesionālu latviešu valodu."], }, ) assert response.status_code == 200 assert ( response.json()["manifest"]["training_request"]["hub_model_id"] == "example-user/custom-model" ) def test_human_training_build_requires_session_when_auth_enabled( monkeypatch, tmp_path: Path ) -> None: client = TestClient(human_training_space_app.app) monkeypatch.setattr(human_training_space_app, "AUTH_REQUIRED", True) monkeypatch.setattr( human_training_space_app, "USERS_FILE", tmp_path / "human-training-users.json" ) monkeypatch.setattr(human_training_space_app, "PERSISTENT_DIR", tmp_path) human_training_space_app.SESSION_STORE.clear() response = client.post( "/api/human-training/build", json={ "dataset_repo": "example-user/memory-dataset", "hub_model_id": "example-user/custom-model", "profile_facts": ["Man vajag profesionālu latviešu valodu."], }, ) assert response.status_code == 401 def test_human_training_build_stages_manifest_for_private_space( monkeypatch, tmp_path: Path ) -> None: client = TestClient(human_training_space_app.app) monkeypatch.setattr(human_training_space_app, "PERSISTENT_DIR", tmp_path) response = client.post( "/api/human-training/build", json={ "dataset_repo": "example-user/memory-dataset", "hub_model_id": "example-user/custom-model", "continue_model_path": "runs/latest", "profile_facts": ["Man vajag profesionālu latviešu valodu."], }, ) assert response.status_code == 200 body = response.json() assert body["manifest"]["training_request"]["hub_model_id"] == "example-user/custom-model" assert body["manifest"]["training_request"]["continue_model_path"] == "runs/latest" def test_human_training_build_stages_manifest_for_logged_in_user_when_auth_enabled( monkeypatch, tmp_path: Path ) -> None: client = TestClient(human_training_space_app.app) monkeypatch.setattr(human_training_space_app, "AUTH_REQUIRED", True) monkeypatch.setattr( human_training_space_app, "USERS_FILE", tmp_path / "human-training-users.json" ) monkeypatch.setattr(human_training_space_app, "PERSISTENT_DIR", tmp_path) human_training_space_app.SESSION_STORE.clear() register_response = client.post( "/api/auth/register", json={ "full_name": "Māris Ozols", "email": "maris@example.com", "password": "drosha-parole-123", "role": "owner", }, ) token = register_response.json()["token"] response = client.post( "/api/human-training/build", headers={"X-Session-Token": token}, json={ "dataset_repo": "example-user/memory-dataset", "hub_model_id": "example-user/custom-model", "continue_model_path": "runs/latest", "profile_facts": ["Man vajag profesionālu latviešu valodu."], }, ) assert response.status_code == 200 body = response.json() assert body["manifest"]["training_request"]["hub_model_id"] == "example-user/custom-model" assert body["manifest"]["training_request"]["continue_model_path"] == "runs/latest"