//! 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": }`. 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")); } }