# 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 mounting only the html-preview router on 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 # Each test starts with an empty in-memory store. html_preview_module._PREVIEWS.clear() 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 # The doctype + base + body are present. 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
") # Force-age the stored entry past the TTL by rewinding monotonic. 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 # And the entry is 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) # Pin the cap low so the test is cheap. mod.MAX_LIVE_PREVIEWS = 4 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"]) # Force monotonic progression so eviction order is deterministic. time.sleep(0.001) assert len(mod._PREVIEWS) == mod.MAX_LIVE_PREVIEWS # The two oldest tokens (urls[0], urls[1]) must have been evicted. for old in urls[:2]: token_id = old.rsplit("/", 1)[-1] assert token_id not in mod._PREVIEWS # Newer tokens are still present. for fresh in urls[-mod.MAX_LIVE_PREVIEWS :]: token_id = fresh.rsplit("/", 1)[-1] assert token_id in mod._PREVIEWS