proxy downloads

This commit is contained in:
2025-11-09 12:01:19 +01:00
parent d24d2c9249
commit 81571e4c64
9 changed files with 383 additions and 71 deletions
+16 -1
View File
@@ -136,10 +136,17 @@ pub struct TestApp {
impl TestApp {
pub async fn new() -> Result<Self> {
Self::with_config(|_| {}).await
}
pub async fn with_config<F>(configure: F) -> Result<Self>
where
F: FnOnce(&mut AppConfig),
{
let database_url = env::var("TEST_DATABASE_URL")
.context("TEST_DATABASE_URL must be set for integration tests")?;
let config = AppConfig {
let mut config = AppConfig {
database_url: database_url.clone(),
database_max_pool_size: db::DEFAULT_MAX_POOL_SIZE,
server_host: "127.0.0.1".to_string(),
@@ -156,6 +163,7 @@ impl TestApp {
refresh_cookie_secure: false,
refresh_cookie_domain: None,
cors_allowed_origin: None,
proxy_downloads: false,
aws_endpoint_url: None,
aws_access_key_id: None,
aws_secret_access_key: None,
@@ -170,6 +178,8 @@ impl TestApp {
webauthn_rp_name: "Papercrate".to_string(),
};
configure(&mut config);
let pool = db::init_pool_with_size(&config.database_url, config.database_max_pool_size)?;
prepare_database(&pool).await?;
@@ -206,6 +216,11 @@ impl TestApp {
Ok(())
}
#[allow(dead_code)]
pub async fn tenant_id(&self) -> Result<Uuid> {
self.ensure_default_tenant().await
}
#[allow(dead_code)]
pub fn storage(&self) -> Arc<FakeStorage> {
self.storage.clone()
+100 -1
View File
@@ -3,12 +3,14 @@ mod common;
use anyhow::{anyhow, Result};
use axum::http::StatusCode;
use common::{acquire_db_lock, body_to_vec, TestApp, UploadExtras};
use diesel::prelude::*;
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use uuid::Uuid;
use papercrate::jobs::{mark_job_succeeded, JOB_PURGE_DOCUMENT};
use papercrate::models::Job;
use papercrate::models::{Job, NewDocumentAsset, NewDocumentAssetObject};
use papercrate::schema::{document_asset_objects, document_assets};
use papercrate::workers::{purge::PurgeDocumentJob, JobExecution, JobHandler};
use std::sync::Arc;
#[derive(Deserialize)]
@@ -64,6 +66,19 @@ struct DocumentAssetInfo {
asset_type: String,
}
#[derive(Deserialize)]
struct AssetProxyDetail {
id: Uuid,
objects: Vec<AssetProxyObject>,
}
#[derive(Deserialize)]
struct AssetProxyObject {
id: Uuid,
url: Option<String>,
expires_at: Option<i64>,
}
#[derive(Deserialize)]
struct DocumentListItem {
id: Uuid,
@@ -277,6 +292,90 @@ async fn upload_document_with_custom_title_sets_filename() -> Result<()> {
Ok(())
}
#[tokio::test]
async fn asset_detail_uses_proxy_urls_when_configured() -> Result<()> {
let _lock = acquire_db_lock().await;
let app = TestApp::with_config(|config| config.proxy_downloads = true).await?;
let tenant_id = app.tenant_id().await?;
let username = "proxy-assets";
let password = "secret";
app.insert_user(username, password, "admin").await?;
let token = app.login_token(username, password).await?;
let upload = app
.upload_document(
"/api/documents",
"proxy.pdf",
"application/pdf",
b"dummy",
None,
&token,
)
.await?;
assert_eq!(upload.status(), StatusCode::CREATED);
let body = body_to_vec(upload.into_body()).await?;
let detail: DocumentDetail = serde_json::from_slice(&body)?;
let document = detail.document;
let version = document
.current_version
.as_ref()
.ok_or_else(|| anyhow!("current version missing"))?;
let mut conn = app
.state
.db_for_tenant(tenant_id)
.map_err(|err| anyhow!("tenant connection: {err:?}"))?;
let asset_id = Uuid::new_v4();
let object_id = Uuid::new_v4();
diesel::insert_into(document_assets::table)
.values(&NewDocumentAsset {
id: asset_id,
document_version_id: version.id,
asset_type: "preview".to_string(),
mime_type: "image/png".to_string(),
metadata: json!({}),
cardinality: Some(1),
tenant_id,
})
.execute(&mut conn)?;
diesel::insert_into(document_asset_objects::table)
.values(&NewDocumentAssetObject {
id: object_id,
asset_id,
ordinal: 1,
s3_key: "objects/preview.png".to_string(),
metadata: json!({}),
tenant_id,
})
.execute(&mut conn)?;
drop(conn);
let response = app
.get(&format!("/api/assets/{asset_id}"), Some(&token))
.await?;
assert_eq!(response.status(), StatusCode::OK);
let body = body_to_vec(response.into_body()).await?;
let asset_detail: AssetProxyDetail = serde_json::from_slice(&body)?;
assert_eq!(asset_detail.id, asset_id);
assert_eq!(asset_detail.objects.len(), 1);
let object = &asset_detail.objects[0];
assert_eq!(object.id, object_id);
let url = object
.url
.as_deref()
.ok_or_else(|| anyhow!("missing url"))?;
assert!(url.starts_with("/download/"));
assert!(object.expires_at.is_none());
app.cleanup().await?;
Ok(())
}
#[tokio::test]
async fn document_list_sorting_controls() -> Result<()> {
let _lock = acquire_db_lock().await;