# SPDX-License-Identifier: AGPL-3.0-only # Copyright 2026-present the Unsloth AI Inc. team. All rights reserved. """Tests for the /api/preview/html route (interactive HTML preview).""" import sys import time from pathlib import Path import pytest from fastapi import FastAPI from fastapi.testclient import TestClient _BACKEND_ROOT = Path(__file__).resolve().parents[1] if str(_BACKEND_ROOT) not in sys.path: sys.path.insert(0, str(_BACKEND_ROOT)) @pytest.fixture def preview_app(tmp_path, monkeypatch): """Standalone app with only the html-preview router and a clean store.""" from auth import storage from auth.authentication import create_access_token monkeypatch.setattr(storage, "DB_PATH", tmp_path / "auth.db") monkeypatch.setattr(storage, "_BOOTSTRAP_PW_PATH", tmp_path / ".bootstrap_password") monkeypatch.setattr(storage, "_bootstrap_password", None) import secrets as _secrets storage.create_initial_user( username = storage.DEFAULT_ADMIN_USERNAME, password = "human-password-123", jwt_secret = _secrets.token_urlsafe(64), must_change_password = False, ) from routes.html_preview import router as html_preview_router from routes import html_preview as html_preview_module html_preview_module._PREVIEWS.clear() # fresh store per test app = FastAPI() app.include_router( html_preview_router, prefix = "/api/preview/html", tags = ["html-preview"] ) token = create_access_token(storage.DEFAULT_ADMIN_USERNAME) return app, token, html_preview_module class TestPostHtmlPreview: def test_post_requires_auth(self, preview_app): app, _token, _mod = preview_app c = TestClient(app) r = c.post("/api/preview/html", json = {"source": "
same
"}, headers = {"Authorization": f"Bearer {token}"}, ) assert r.status_code == 200 urls.add(r.json()["url"]) assert len(urls) == 5 class TestGetHtmlPreview: def _create(self, app, token, source): c = TestClient(app) r = c.post( "/api/preview/html", json = {"source": source}, headers = {"Authorization": f"Bearer {token}"}, ) return r.json()["url"] def test_get_serves_stored_html_with_overriding_csp(self, preview_app): app, token, _mod = preview_app url = self._create(app, token, "") c = TestClient(app) r = c.get(url) assert r.status_code == 200 body = r.text assert "" in body.lower() assert 'nope
") c = TestClient(app) r = c.get(url) # no Authorization header assert r.status_code == 200 def test_get_unknown_token_is_404(self, preview_app): app, _token, _mod = preview_app c = TestClient(app) r = c.get("/api/preview/html/totally-not-a-real-token") assert r.status_code == 404 def test_get_expired_token_is_404(self, preview_app): app, token, mod = preview_app url = self._create(app, token, "aging
") # Rewind monotonic past the TTL. token_id = url.rsplit("/", 1)[-1] created, src = mod._PREVIEWS[token_id] mod._PREVIEWS[token_id] = (created - (mod.PREVIEW_TTL_SECONDS + 5), src) c = TestClient(app) r = c.get(url) assert r.status_code == 404 # Swept on access. assert token_id not in mod._PREVIEWS class TestEviction: def test_overflow_evicts_oldest_entries(self, preview_app): app, token, mod = preview_app c = TestClient(app) mod.MAX_LIVE_PREVIEWS = 4 # cheap test urls = [] for i in range(6): r = c.post( "/api/preview/html", json = {"source": f"{i}
"}, headers = {"Authorization": f"Bearer {token}"}, ) urls.append(r.json()["url"]) time.sleep(0.001) # deterministic monotonic order assert len(mod._PREVIEWS) == mod.MAX_LIVE_PREVIEWS # Oldest two evicted. for old in urls[:2]: token_id = old.rsplit("/", 1)[-1] assert token_id not in mod._PREVIEWS # Newest survive. for fresh in urls[-mod.MAX_LIVE_PREVIEWS :]: token_id = fresh.rsplit("/", 1)[-1] assert token_id in mod._PREVIEWS