feat(billing): implement tenant subscription entitlements system (milestones 0-6)
This commit is contained in:
116
control/api/tests/s3_docs_gated.rs
Normal file
116
control/api/tests/s3_docs_gated.rs
Normal file
@@ -0,0 +1,116 @@
|
||||
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");
|
||||
}
|
||||
Reference in New Issue
Block a user