use std::sync::Arc;
use actix_web::http::StatusCode;
use actix_web::test;
use bytes::Bytes;
use solid_pod_rs::security::DotfileAllowlist;
use solid_pod_rs::storage::memory::MemoryBackend;
use solid_pod_rs::storage::Storage;
use solid_pod_rs_server::{build_app, AppState, NodeInfoMeta, PodCreateLimiter};
async fn make_state() -> AppState {
let backend = Arc::new(MemoryBackend::new());
let ttl = r#"
@prefix acl: <http://www.w3.org/ns/auth/acl#> .
@prefix foaf: <http://xmlns.com/foaf/0.1/> .
<#public> a acl:Authorization ;
acl:agentClass foaf:Agent ;
acl:accessTo </> ;
acl:default </> ;
acl:mode acl:Read, acl:Write, acl:Append, acl:Control .
"#;
backend
.put(
"/.acl",
Bytes::copy_from_slice(ttl.as_bytes()),
"text/turtle",
)
.await
.unwrap();
backend
.put(
"/site/.meta",
Bytes::from_static(b"# container marker"),
"text/plain",
)
.await
.unwrap();
AppState {
storage: backend,
dotfiles: Arc::new(DotfileAllowlist::with_defaults()),
body_cap: 10_000_000,
nodeinfo: NodeInfoMeta {
software_name: "solid-pod-rs-server".into(),
software_version: "0.4.0".into(),
open_registrations: false,
total_users: 0,
base_url: "https://pod.example".into(),
},
mashlib: solid_pod_rs::MashlibConfig::default(),
mashlib_cdn: None,
pay_config: solid_pod_rs::payments::PayConfig::default(),
data_root: None,
pod_create_limiter: Arc::new(PodCreateLimiter::default()),
}
}
#[actix_web::test]
async fn container_get_html_with_index_serves_index_html() {
let state = make_state().await;
let storage = state.storage.clone();
let html_content = b"<html><body>Hello from index</body></html>";
storage
.put(
"/site/index.html",
Bytes::copy_from_slice(html_content),
"text/html",
)
.await
.unwrap();
let app = test::init_service(build_app(state)).await;
let req = test::TestRequest::get()
.uri("/site/")
.insert_header(("accept", "text/html"))
.to_request();
let resp = test::call_service(&app, req).await;
assert_eq!(resp.status(), StatusCode::OK);
let ct = resp
.headers()
.get("content-type")
.unwrap()
.to_str()
.unwrap();
assert!(
ct.contains("text/html"),
"content-type should be text/html, got: {ct}"
);
let body = test::read_body(resp).await;
assert_eq!(&body[..], html_content);
}
#[actix_web::test]
async fn container_get_turtle_ignores_index_html() {
let state = make_state().await;
let storage = state.storage.clone();
storage
.put(
"/site/index.html",
Bytes::from_static(b"<html>ignored</html>"),
"text/html",
)
.await
.unwrap();
let app = test::init_service(build_app(state)).await;
let req = test::TestRequest::get()
.uri("/site/")
.insert_header(("accept", "text/turtle"))
.to_request();
let resp = test::call_service(&app, req).await;
assert_eq!(resp.status(), StatusCode::OK);
let ct = resp
.headers()
.get("content-type")
.unwrap()
.to_str()
.unwrap();
assert!(
ct.contains("application/ld+json"),
"expected RDF container listing content-type, got: {ct}"
);
}
#[actix_web::test]
async fn container_get_html_without_index_falls_through_to_rdf() {
let state = make_state().await;
let app = test::init_service(build_app(state)).await;
let req = test::TestRequest::get()
.uri("/site/")
.insert_header(("accept", "text/html"))
.to_request();
let resp = test::call_service(&app, req).await;
assert_eq!(resp.status(), StatusCode::OK);
let ct = resp
.headers()
.get("content-type")
.unwrap()
.to_str()
.unwrap();
assert!(
ct.contains("application/ld+json"),
"expected RDF container listing (mashlib off), got: {ct}"
);
}
#[actix_web::test]
async fn container_get_jsonld_ignores_index_html() {
let state = make_state().await;
let storage = state.storage.clone();
storage
.put(
"/site/index.html",
Bytes::from_static(b"<html>nope</html>"),
"text/html",
)
.await
.unwrap();
let app = test::init_service(build_app(state)).await;
let req = test::TestRequest::get()
.uri("/site/")
.insert_header(("accept", "application/ld+json"))
.to_request();
let resp = test::call_service(&app, req).await;
assert_eq!(resp.status(), StatusCode::OK);
let ct = resp
.headers()
.get("content-type")
.unwrap()
.to_str()
.unwrap();
assert!(
ct.contains("application/ld+json"),
"Solid client should get JSON-LD, got: {ct}"
);
}
#[actix_web::test]
async fn container_get_browser_accept_with_index_serves_html() {
let state = make_state().await;
let storage = state.storage.clone();
let html = b"<html><body>Browser view</body></html>";
storage
.put(
"/site/index.html",
Bytes::copy_from_slice(html),
"text/html",
)
.await
.unwrap();
let app = test::init_service(build_app(state)).await;
let req = test::TestRequest::get()
.uri("/site/")
.insert_header((
"accept",
"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
))
.to_request();
let resp = test::call_service(&app, req).await;
assert_eq!(resp.status(), StatusCode::OK);
let ct = resp
.headers()
.get("content-type")
.unwrap()
.to_str()
.unwrap();
assert!(ct.contains("text/html"));
let body = test::read_body(resp).await;
assert_eq!(&body[..], html);
}