M7.1 smoke harness: lift auth to compliance-core + compliance-smoke service (#83)
CI / Check (push) Has been cancelled
CI / Detect Changes (push) Has been cancelled
CI / Deploy Agent (push) Has been cancelled
CI / Deploy Dashboard (push) Has been cancelled
CI / Deploy Docs (push) Has been cancelled
CI / Deploy MCP (push) Has been cancelled
CI / Check (push) Has been cancelled
CI / Detect Changes (push) Has been cancelled
CI / Deploy Agent (push) Has been cancelled
CI / Deploy Dashboard (push) Has been cancelled
CI / Deploy Docs (push) Has been cancelled
CI / Deploy MCP (push) Has been cancelled
This commit was merged in pull request #83.
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
//! Database helpers shared across the workspace.
|
||||
//!
|
||||
//! `tenant_filter` returns the BSON filter that every query and update
|
||||
//! against a tenant-scoped collection MUST include. Centralising it here
|
||||
//! makes the rule grep-able and keeps query call-sites from accidentally
|
||||
//! omitting it.
|
||||
//!
|
||||
//! Future work (M7.2+): each collection model grows a `tenant_id` field
|
||||
//! and every `find` / `update_*` / `delete_*` call gets this filter
|
||||
//! merged in. The migration to per-collection scoping is tracked
|
||||
//! separately — this helper is the building block.
|
||||
|
||||
use bson::{doc, Document};
|
||||
|
||||
use crate::TenantContext;
|
||||
|
||||
/// Returns `{ "tenant_id": <ctx.tenant_id> }`. Merge this into every
|
||||
/// query filter against a tenant-scoped collection.
|
||||
///
|
||||
/// Use [`tenant_filter_merge`] when you need to combine it with other
|
||||
/// query conditions — it preserves both halves without overwriting.
|
||||
pub fn tenant_filter(ctx: &TenantContext) -> Document {
|
||||
doc! { "tenant_id": &ctx.tenant_id }
|
||||
}
|
||||
|
||||
/// Returns the tenant filter merged with caller-supplied conditions.
|
||||
/// The tenant_id always wins on key conflict — callers cannot
|
||||
/// accidentally override the scoping.
|
||||
pub fn tenant_filter_merge(ctx: &TenantContext, mut extra: Document) -> Document {
|
||||
extra.insert("tenant_id", &ctx.tenant_id);
|
||||
extra
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::TenantStatus;
|
||||
|
||||
fn ctx() -> TenantContext {
|
||||
TenantContext {
|
||||
tenant_id: "t-abc".to_string(),
|
||||
tenant_slug: "acme".to_string(),
|
||||
org_roles: vec![],
|
||||
products: vec![],
|
||||
plan: "starter".to_string(),
|
||||
status: TenantStatus::Active,
|
||||
user_id: "u-1".to_string(),
|
||||
user_name: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn produces_tenant_id_filter() {
|
||||
let f = tenant_filter(&ctx());
|
||||
assert_eq!(f.get_str("tenant_id"), Ok("t-abc"));
|
||||
assert_eq!(f.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn merge_preserves_extra_conditions() {
|
||||
let extra = doc! { "status": "open", "severity": "high" };
|
||||
let f = tenant_filter_merge(&ctx(), extra);
|
||||
assert_eq!(f.get_str("tenant_id"), Ok("t-abc"));
|
||||
assert_eq!(f.get_str("status"), Ok("open"));
|
||||
assert_eq!(f.get_str("severity"), Ok("high"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn merge_overrides_caller_tenant_id() {
|
||||
let extra = doc! { "tenant_id": "evil-other", "status": "open" };
|
||||
let f = tenant_filter_merge(&ctx(), extra);
|
||||
assert_eq!(f.get_str("tenant_id"), Ok("t-abc"));
|
||||
assert_eq!(f.get_str("status"), Ok("open"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user