/** * API Test Plugin - Admin Components * * Provides a dashboard widget and test page for exercising plugin APIs. */ import { Play, CheckCircle, XCircle, CircleNotch, Database, Key, Globe, FileText, ImageSquare, Terminal, ArrowsClockwise, } from "@phosphor-icons/react"; import type { PluginAdminExports } from "emdash"; import { apiFetch, getErrorMessage, parseApiResponse } from "emdash/plugin-utils"; import * as React from "react"; // ============================================================================= // Types // ============================================================================= interface TestResult { name: string; status: "pending" | "running" | "success" | "error"; duration?: number; error?: string; data?: unknown; } interface ApiTestResults { plugin: { id: string; version: string }; log: string; kv: { key: string; value: unknown; cleaned: boolean }; storage: { id: string; entry: unknown; cleaned: boolean }; content: { available: boolean; canWrite: boolean; sampleCount: number }; media: { available: boolean; canWrite: boolean; sampleCount: number }; http: { available: boolean; testStatus?: number; error?: string }; } // ============================================================================= // Dashboard Widget // ============================================================================= function ApiTestWidget() { const [lastRun, setLastRun] = React.useState(null); const [results, setResults] = React.useState(null); const [isRunning, setIsRunning] = React.useState(false); const [error, setError] = React.useState(null); const runTests = async () => { setIsRunning(true); setError(null); try { const response = await apiFetch("/_emdash/api/plugins/api-test/test/all", { method: "POST", headers: { "Content-Type": "application/json" }, body: "{}", }); if (response.ok) { const data = await parseApiResponse<{ results: ApiTestResults }>(response); setResults(data.results); setLastRun(new Date()); } else { setError(await getErrorMessage(response, "Test failed")); } } catch (e) { setError(e instanceof Error ? e.message : "Test failed"); } finally { setIsRunning(false); } }; const apiStatus = React.useMemo(() => { if (!results) return []; return [ { name: "Plugin", ok: !!results.plugin?.id, icon: Terminal }, { name: "KV", ok: results.kv?.cleaned, icon: Key }, { name: "Storage", ok: results.storage?.cleaned, icon: Database }, { name: "Content", ok: results.content?.available, icon: FileText }, { name: "Media", ok: results.media?.available, icon: ImageSquare }, { name: "HTTP", ok: results.http?.testStatus === 200, icon: Globe }, ]; }, [results]); return (
{error &&
{error}
} {results ? (
{apiStatus.map(({ name, ok, icon: Icon }) => (
{name} {ok ? ( ) : ( )}
))}
) : (
No test results yet
)}
{lastRun && ( Last run: {lastRun.toLocaleTimeString()} )}
); } // ============================================================================= // Test Page // ============================================================================= const API_TESTS = [ { id: "plugin-info", name: "Plugin Info", route: "plugin/info", icon: Terminal, }, { id: "kv-set", name: "KV Set", route: "kv/set", icon: Key, body: { key: "admin-test", value: { from: "admin" } }, }, { id: "kv-get", name: "KV Get", route: "kv/get", icon: Key, body: { key: "admin-test" }, }, { id: "kv-list", name: "KV List", route: "kv/list", icon: Key }, { id: "storage-put", name: "Storage Put", route: "storage/logs/put", icon: Database, body: { level: "info", message: "Test from admin" }, }, { id: "storage-query", name: "Storage Query", route: "storage/logs/query", icon: Database, body: { limit: 5 }, }, { id: "content-list", name: "Content List", route: "content/list", icon: FileText, }, { id: "media-list", name: "Media List", route: "media/list", icon: ImageSquare, }, { id: "http-fetch", name: "HTTP Fetch", route: "http/fetch", icon: Globe, body: { url: "https://httpbin.org/get" }, }, { id: "log-test", name: "Logging", route: "log/test", icon: Terminal }, ]; function TestPage() { const [results, setResults] = React.useState>({}); const [isRunningAll, setIsRunningAll] = React.useState(false); const runTest = async (testId: string, route: string, body?: unknown) => { setResults((prev) => ({ ...prev, [testId]: { name: testId, status: "running" }, })); const start = Date.now(); try { const response = await apiFetch(`/_emdash/api/plugins/api-test/${route}`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body || {}), }); const duration = Date.now() - start; if (response.ok) { const data = await parseApiResponse(response); setResults((prev) => ({ ...prev, [testId]: { name: testId, status: "success", duration, data }, })); } else { const errorMsg = await getErrorMessage(response, "Failed"); setResults((prev) => ({ ...prev, [testId]: { name: testId, status: "error", duration, error: errorMsg, }, })); } } catch (e) { setResults((prev) => ({ ...prev, [testId]: { name: testId, status: "error", duration: Date.now() - start, error: e instanceof Error ? e.message : "Failed", }, })); } }; const runAllTests = async () => { setIsRunningAll(true); for (const test of API_TESTS) { await runTest(test.id, test.route, test.body); } setIsRunningAll(false); }; const successCount = Object.values(results).filter((r) => r.status === "success").length; const errorCount = Object.values(results).filter((r) => r.status === "error").length; return (

API Tests

Test all plugin v2 APIs

{Object.keys(results).length > 0 && (
{successCount} passed {errorCount > 0 && ( <> {" / "} {errorCount} failed )}
)}
{API_TESTS.map((test) => { const result = results[test.id]; const Icon = test.icon; return (
{test.name}
{result?.status === "success" && ( {result.duration}ms )} {result?.status === "running" ? ( ) : result?.status === "success" ? ( ) : result?.status === "error" ? ( ) : null}
POST /_emdash/api/plugins/api-test/{test.route}
{result?.status === "error" && (
{result.error}
)} {result?.status === "success" && result.data && (
									{JSON.stringify(result.data, null, 2)}
								
)}
); })}
); } // ============================================================================= // Exports // ============================================================================= export const widgets: PluginAdminExports["widgets"] = { "api-status": ApiTestWidget, }; export const pages: PluginAdminExports["pages"] = { "/test": TestPage, };