Add Mini App artifact viewer tests
This commit is contained in:
@@ -6,7 +6,7 @@ use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
use axum::extract::{Path as AxumPath, Query, State};
|
||||
use axum::http::{header, HeaderMap, StatusCode};
|
||||
use axum::response::{Html, IntoResponse, Response};
|
||||
use axum::response::{Html, Response};
|
||||
use axum::routing::{get, post};
|
||||
use axum::{Json, Router};
|
||||
use channel_gateway_core::ProfileRecord;
|
||||
@@ -462,24 +462,28 @@ async fn artifact_viewer(
|
||||
AxumPath((turn_id, file_id)): AxumPath<(String, String)>,
|
||||
Query(query): Query<ArtifactViewerQuery>,
|
||||
) -> Result<Html<String>, StatusCode> {
|
||||
let _ = state;
|
||||
let meta = crate::gateway::lookup_artifact(&turn_id, &file_id)
|
||||
.await
|
||||
.ok_or(StatusCode::NOT_FOUND)?;
|
||||
let token_hint = query.token.as_deref().unwrap_or("");
|
||||
let extension = std::path::Path::new(&meta.file_name)
|
||||
.extension()
|
||||
.and_then(|e| e.to_str())
|
||||
.unwrap_or("")
|
||||
.to_ascii_lowercase();
|
||||
let render_mode = match extension.as_str() {
|
||||
"html" | "htm" => "html",
|
||||
"md" | "markdown" => "markdown",
|
||||
"json" => "json",
|
||||
"jsx" | "tsx" => "react",
|
||||
_ => "code",
|
||||
};
|
||||
let escaped_name = meta.file_name.replace('<', "<").replace('>', ">");
|
||||
let page = format!(
|
||||
Ok(Html(build_artifact_viewer_page(
|
||||
&meta.file_name,
|
||||
&turn_id,
|
||||
&file_id,
|
||||
token_hint,
|
||||
)))
|
||||
}
|
||||
|
||||
fn build_artifact_viewer_page(
|
||||
file_name: &str,
|
||||
turn_id: &str,
|
||||
file_id: &str,
|
||||
token_hint: &str,
|
||||
) -> String {
|
||||
let escaped_name = escape_html(file_name);
|
||||
let render_mode = artifact_render_mode(file_name);
|
||||
format!(
|
||||
r##"<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
@@ -605,8 +609,30 @@ try {{
|
||||
</script>
|
||||
</body>
|
||||
</html>"##
|
||||
);
|
||||
Ok(Html(page))
|
||||
)
|
||||
}
|
||||
|
||||
fn artifact_render_mode(file_name: &str) -> &'static str {
|
||||
let extension = std::path::Path::new(file_name)
|
||||
.extension()
|
||||
.and_then(|e| e.to_str())
|
||||
.unwrap_or("")
|
||||
.to_ascii_lowercase();
|
||||
match extension.as_str() {
|
||||
"html" | "htm" => "html",
|
||||
"md" | "markdown" => "markdown",
|
||||
"json" => "json",
|
||||
"jsx" | "tsx" => "react",
|
||||
_ => "code",
|
||||
}
|
||||
}
|
||||
|
||||
fn escape_html(value: &str) -> String {
|
||||
value
|
||||
.replace('&', "&")
|
||||
.replace('<', "<")
|
||||
.replace('>', ">")
|
||||
.replace('"', """)
|
||||
}
|
||||
|
||||
async fn authorize(
|
||||
@@ -804,3 +830,55 @@ impl From<WorkerManagerError> for MiniAppError {
|
||||
Self::Worker(error)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{artifact_render_mode, build_artifact_viewer_page, escape_html};
|
||||
|
||||
#[test]
|
||||
fn artifact_render_mode_classifies_supported_extensions() {
|
||||
assert_eq!(artifact_render_mode("report.html"), "html");
|
||||
assert_eq!(artifact_render_mode("README.MD"), "markdown");
|
||||
assert_eq!(artifact_render_mode("data.json"), "json");
|
||||
assert_eq!(artifact_render_mode("widget.tsx"), "react");
|
||||
assert_eq!(artifact_render_mode("component.jsx"), "react");
|
||||
assert_eq!(artifact_render_mode("main.rs"), "code");
|
||||
assert_eq!(artifact_render_mode("no-extension"), "code");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn escape_html_escapes_special_characters() {
|
||||
assert_eq!(
|
||||
escape_html("artifact & <preview> \"name\""),
|
||||
"artifact & <preview> "name""
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn artifact_viewer_page_uses_render_mode_and_escaped_name() {
|
||||
let page = build_artifact_viewer_page(
|
||||
"artifact <demo>.md",
|
||||
"turn-1",
|
||||
"file-1",
|
||||
"session-token",
|
||||
);
|
||||
|
||||
assert!(page.contains("<title>artifact <demo>.md</title>"));
|
||||
assert!(page.contains("<h1>artifact <demo>.md</h1>"));
|
||||
assert!(page.contains("const turnId = \"turn-1\";"));
|
||||
assert!(page.contains("const fileId = \"file-1\";"));
|
||||
assert!(page.contains("let token = \"session-token\";"));
|
||||
assert!(page.contains("const renderMode = \"markdown\";"));
|
||||
assert!(page.contains("marked.parse(text)"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn artifact_viewer_page_includes_react_runtime_for_tsx() {
|
||||
let page = build_artifact_viewer_page("demo.tsx", "turn-2", "file-2", "");
|
||||
|
||||
assert!(page.contains("const renderMode = \"react\";"));
|
||||
assert!(page.contains("renderReactArtifact(el, text);"));
|
||||
assert!(page.contains("react@18/umd/react.production.min.js"));
|
||||
assert!(page.contains("@babel/standalone/babel.min.js"));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user