use api::s3_docs::{DocsConfig, DocsStore}; use uuid::Uuid; fn s3_env_ready() -> bool { // Gate integration tests without requiring `-- --ignored`. // If CI/local wants these tests to run, it must provide S3 env vars. let required = [ "CONTROL_S3_ENDPOINT", "CONTROL_S3_ACCESS_KEY_ID", "CONTROL_S3_SECRET_ACCESS_KEY", "CONTROL_S3_BUCKET_DOCS", ]; required .iter() .all(|k| std::env::var(k).ok().is_some_and(|v| !v.trim().is_empty())) } #[tokio::test] async fn s3_docs_roundtrip_put_get_list_delete() { if !s3_env_ready() { eprintln!("skipping: missing S3 env (see S3_PLAN.md)"); return; } let cfg = DocsConfig::from_env().expect("missing S3 env (see S3_PLAN.md)"); let store = DocsStore::new(cfg) .await .expect("failed to init docs store"); let tenant_id = Uuid::new_v4().to_string(); let doc_type = "test"; let doc_id = Uuid::new_v4().to_string(); let filename = "hello.txt"; let key = store .key_for(&tenant_id, doc_type, &doc_id, filename) .expect("invalid key"); store .put_for_tenant( &tenant_id, &key, b"hello".to_vec(), Some("text/plain".to_string()), ) .await .expect("put failed"); let (bytes, _ct) = store .get_bytes_for_tenant(&tenant_id, &key) .await .expect("get failed"); assert_eq!(bytes, b"hello"); let prefix = format!("{}{}", store.prefix(), tenant_id); let objects = store .list_for_tenant(&tenant_id, &format!("{prefix}/")) .await .expect("list failed"); assert!(objects.iter().any(|o| o.key == key)); store .delete_for_tenant(&tenant_id, &key) .await .expect("delete failed"); } #[tokio::test] async fn s3_docs_tenant_prefix_isolation() { if !s3_env_ready() { eprintln!("skipping: missing S3 env (see S3_PLAN.md)"); return; } let cfg = DocsConfig::from_env().expect("missing S3 env (see S3_PLAN.md)"); let store = DocsStore::new(cfg) .await .expect("failed to init docs store"); let tenant_a = Uuid::new_v4().to_string(); let tenant_b = Uuid::new_v4().to_string(); let doc_type = "test"; let doc_id = Uuid::new_v4().to_string(); let filename = "hello.txt"; let key_a = store .key_for(&tenant_a, doc_type, &doc_id, filename) .expect("invalid key"); store .put_for_tenant( &tenant_a, &key_a, b"hello-a".to_vec(), Some("text/plain".to_string()), ) .await .expect("put failed"); let prefix_a = format!("{}{tenant_a}/", store.prefix()); let prefix_b = format!("{}{tenant_b}/", store.prefix()); let objects_a = store .list_for_tenant(&tenant_a, &prefix_a) .await .expect("list a failed"); let objects_b = store .list_for_tenant(&tenant_b, &prefix_b) .await .expect("list b failed"); assert!(objects_a.iter().any(|o| o.key == key_a)); assert!(!objects_b.iter().any(|o| o.key == key_a)); store .delete_for_tenant(&tenant_a, &key_a) .await .expect("delete failed"); }