autumn_plugin_markstream/
plugin.rs1use std::borrow::Cow;
8
9use autumn_web::app::AppBuilder;
10use autumn_web::plugin::Plugin;
11
12use crate::assets::MARKSTREAM_ASSETS;
13
14pub const PLUGIN_NAME: &str = "autumn-plugin-markstream";
16
17#[derive(Debug, Default, Clone, Copy)]
33#[must_use]
34pub struct MarkstreamPlugin;
35
36impl MarkstreamPlugin {
37 pub const fn new() -> Self {
39 Self
40 }
41}
42
43impl Plugin for MarkstreamPlugin {
44 fn name(&self) -> Cow<'static, str> {
45 Cow::Borrowed(PLUGIN_NAME)
46 }
47
48 fn build(self, app: AppBuilder) -> AppBuilder {
49 app.plugin_assets(&MARKSTREAM_ASSETS)
50 }
51}
52
53#[cfg(test)]
54mod tests {
55 use super::*;
56 use crate::assets::{CORE_JS, INIT_JS, MARKSTREAM_ASSETS, MARKSTREAM_ENTRY, PLUGIN_CSS};
57 use autumn_web::assets::{PLUGIN_ASSETS_ROUTE_MARKER, asset_url};
58 use autumn_web::plugin_conformance::{ConformanceConfig, run_conformance};
59 use autumn_web::route_listing::{RouteClassification, RouteSource};
60 use autumn_web::test::{TestApp, TestClient};
61
62 const JS: &str = "text/javascript; 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(MarkstreamPlugin::new()).build()
68 }
69
70 #[tokio::test]
71 async fn init_module_serves_at_its_hashed_url() {
72 let response = client().get(&MARKSTREAM_ASSETS.url(INIT_JS)).send().await;
73 response
74 .assert_ok()
75 .assert_header("content-type", JS)
76 .assert_header("cache-control", IMMUTABLE);
77 assert!(response.text().contains("./core.js"));
78 }
79
80 #[tokio::test]
81 async fn relative_imports_resolve_to_plain_urls_that_serve() {
82 let client = client();
83 for path in [
84 CORE_JS,
85 MARKSTREAM_ENTRY,
86 "vendor/vue/vue.runtime.esm-browser.prod.js",
87 ] {
88 let plain = format!("/static/_plugins/markstream/{path}");
89 let response = client.get(&plain).send().await;
90 response
91 .assert_ok()
92 .assert_header("content-type", JS)
93 .assert_header("cache-control", REVALIDATE);
94 let etag = response.header("etag").expect("etag").to_owned();
95 client
96 .get(&plain)
97 .header("if-none-match", &etag)
98 .send()
99 .await
100 .assert_status(304);
101 }
102 }
103
104 #[tokio::test]
105 async fn stylesheets_serve() {
106 let client = client();
107 for path in [PLUGIN_CSS, "vendor/markstream-vue/index.css"] {
108 client
109 .get(&MARKSTREAM_ASSETS.url(path))
110 .send()
111 .await
112 .assert_ok()
113 .assert_header("content-type", "text/css; charset=utf-8");
114 }
115 }
116
117 #[tokio::test]
118 async fn provenance_and_unknown_paths_are_not_found() {
119 let client = client();
120 for path in [
121 "/static/_plugins/markstream/manifest.json",
122 "/static/_plugins/markstream/licenses/vue.LICENSE",
123 "/static/_plugins/markstream/init.00000000.js",
124 "/static/_plugins/markstream/vendor/markstream-vue/tailwind.js",
125 "/static/_plugins/markstream/../manifest.json",
126 ] {
127 client.get(path).send().await.assert_status(404);
128 }
129 }
130
131 #[tokio::test]
132 async fn asset_url_resolves_the_installed_bundle() {
133 let _client = client();
134 assert_eq!(
135 asset_url(&format!("_plugins/markstream/{INIT_JS}")),
136 MARKSTREAM_ASSETS.url(INIT_JS)
137 );
138 }
139
140 #[test]
141 fn bundle_routes_are_public_plugin_routes() {
142 let app = autumn_web::app().plugin(MarkstreamPlugin::new());
143 let infos = app.plugin_route_infos().expect("route infos");
144 let routes: Vec<_> = infos
145 .iter()
146 .filter(|i| i.path.starts_with("/static/_plugins/markstream/"))
147 .collect();
148 assert_eq!(routes.len(), MARKSTREAM_ASSETS.iter().count() * 2);
149 for info in routes {
150 assert_eq!(info.method, "GET");
151 assert_eq!(info.classification, RouteClassification::Public);
152 assert_eq!(info.middleware, [PLUGIN_ASSETS_ROUTE_MARKER]);
153 assert_eq!(info.source, RouteSource::Plugin(PLUGIN_NAME.to_owned()));
154 }
155 }
156
157 #[test]
158 fn plugin_passes_conformance() {
159 let app = autumn_web::app().plugin(MarkstreamPlugin::new());
160 let infos = app.plugin_route_infos().expect("route infos");
161 let report = run_conformance(&ConformanceConfig::new(PLUGIN_NAME), &infos);
162 assert!(report.passed(), "{}", report.to_text_report());
163 }
164
165 #[tokio::test]
166 async fn installing_the_plugin_twice_is_harmless() {
167 let client = TestApp::new()
168 .plugin(MarkstreamPlugin::new())
169 .plugin(MarkstreamPlugin::new())
170 .build();
171 client
172 .get(&MARKSTREAM_ASSETS.url(INIT_JS))
173 .send()
174 .await
175 .assert_ok();
176 }
177}