autumn_plugin_three/
plugin.rs1use std::borrow::Cow;
7
8use autumn_web::app::AppBuilder;
9use autumn_web::plugin::Plugin;
10
11use crate::assets::THREE_ASSETS;
12
13pub const PLUGIN_NAME: &str = "autumn-plugin-three";
15
16#[derive(Debug, Default)]
32#[must_use]
33pub struct ThreePlugin;
34
35impl ThreePlugin {
36 pub const fn new() -> Self {
38 Self
39 }
40}
41
42impl Plugin for ThreePlugin {
43 fn name(&self) -> Cow<'static, str> {
44 Cow::Borrowed(PLUGIN_NAME)
45 }
46
47 fn build(self, app: AppBuilder) -> AppBuilder {
48 app.plugin_assets(&THREE_ASSETS)
49 }
50}
51
52#[cfg(test)]
53mod tests {
54 use super::*;
55 use crate::assets::{CORE_JS, INIT_JS, MODULE_JS, THREE_ASSETS, THREE_CSS};
56 use autumn_web::assets::{PLUGIN_ASSETS_ROUTE_MARKER, asset_url};
57 use autumn_web::plugin_conformance::{ConformanceConfig, run_conformance};
58 use autumn_web::route_listing::{RouteClassification, RouteSource};
59 use autumn_web::test::{TestApp, TestClient};
60
61 const JS: &str = "text/javascript; charset=utf-8";
62 const CSS: &str = "text/css; charset=utf-8";
63 const IMMUTABLE: &str = "public, max-age=31536000, immutable";
64 const REVALIDATE: &str = "public, max-age=0, must-revalidate";
65
66 fn client() -> TestClient {
67 TestApp::new().plugin(ThreePlugin::new()).build()
68 }
69
70 fn url(path: &str) -> String {
71 THREE_ASSETS.url(path)
72 }
73
74 #[tokio::test]
75 async fn every_file_serves_at_its_fingerprinted_url() {
76 let client = client();
77 for asset in THREE_ASSETS.iter() {
78 let response = client.get(asset.url()).send().await;
79 let is_css = std::path::Path::new(asset.logical_path())
80 .extension()
81 .is_some_and(|e| e.eq_ignore_ascii_case("css"));
82 let expected = if is_css { CSS } else { JS };
83 response
84 .assert_ok()
85 .assert_header("content-type", expected)
86 .assert_header("cache-control", IMMUTABLE);
87 assert_eq!(
88 response.body.as_slice(),
89 asset.bytes(),
90 "{}",
91 asset.logical_path()
92 );
93 }
94 }
95
96 #[tokio::test]
97 async fn module_files_serve_at_plain_urls_with_etags() {
98 let client = client();
100 for path in [CORE_JS, MODULE_JS, INIT_JS, THREE_CSS] {
101 let plain = format!("/static/_plugins/three/{path}");
102 let response = client.get(&plain).send().await;
103 response
104 .assert_ok()
105 .assert_header("cache-control", REVALIDATE);
106 let etag = response.header("etag").expect("etag").to_owned();
107 client
108 .get(&plain)
109 .header("if-none-match", &etag)
110 .send()
111 .await
112 .assert_status(304);
113 }
114 }
115
116 #[tokio::test]
117 async fn unbundled_and_stale_paths_are_not_found() {
118 let client = client();
119 for path in [
120 "/static/_plugins/three/manifest.json",
121 "/static/_plugins/three/THREE-LICENSE",
122 "/static/_plugins/three/init.00000000.js",
123 "/static/_plugins/three/three.webgpu.js",
124 ] {
125 client.get(path).send().await.assert_status(404);
126 }
127 }
128
129 #[tokio::test]
130 async fn asset_url_resolves_the_installed_bundle() {
131 let _client = client();
132 for path in [CORE_JS, MODULE_JS, INIT_JS, THREE_CSS] {
133 assert_eq!(asset_url(&format!("_plugins/three/{path}")), url(path));
134 }
135 }
136
137 #[test]
138 fn bundle_routes_are_public_plugin_routes() {
139 let app = autumn_web::app().plugin(ThreePlugin::new());
140 let infos = app.plugin_route_infos().expect("route infos");
141 let routes: Vec<_> = infos
142 .iter()
143 .filter(|info| info.path.starts_with("/static/_plugins/three/"))
144 .collect();
145 assert_eq!(routes.len(), 20, "ten files, two URLs each: {infos:?}");
146 for info in routes {
147 assert_eq!(info.method, "GET");
148 assert_eq!(info.classification, RouteClassification::Public);
149 assert_eq!(info.middleware, [PLUGIN_ASSETS_ROUTE_MARKER]);
150 assert_eq!(info.source, RouteSource::Plugin(PLUGIN_NAME.to_owned()));
151 }
152 }
153
154 #[test]
155 fn plugin_passes_conformance() {
156 let app = autumn_web::app().plugin(ThreePlugin::new());
157 let infos = app.plugin_route_infos().expect("route infos");
158 let report = run_conformance(&ConformanceConfig::new(PLUGIN_NAME), &infos);
159 assert!(report.passed(), "{}", report.to_text_report());
160 }
161
162 #[tokio::test]
163 async fn installing_the_plugin_twice_is_harmless() {
164 let client = TestApp::new()
165 .plugin(ThreePlugin::new())
166 .plugin(ThreePlugin::new())
167 .build();
168 client.get(&url(INIT_JS)).send().await.assert_ok();
169 }
170}