forge_core/testing/context/
query.rs1use std::collections::HashMap;
4use std::sync::Arc;
5
6use sqlx::PgPool;
7use uuid::Uuid;
8
9use crate::Result;
10use crate::env::{EnvAccess, EnvProvider, MockEnvProvider};
11use crate::function::{AuthContext, RequestMetadata};
12
13pub struct TestQueryContext {
32 pub auth: AuthContext,
34 pub request: RequestMetadata,
36 pool: Option<PgPool>,
38 tenant_id: Option<Uuid>,
40 env_provider: Arc<MockEnvProvider>,
42}
43
44impl TestQueryContext {
45 pub fn builder() -> TestQueryContextBuilder {
47 TestQueryContextBuilder::default()
48 }
49
50 pub fn minimal() -> Self {
52 Self::builder().build()
53 }
54
55 pub fn authenticated(user_id: Uuid) -> Self {
57 Self::builder().as_user(user_id).build()
58 }
59
60 pub fn with_pool(pool: PgPool, user_id: Option<Uuid>) -> Self {
62 let mut builder = Self::builder().with_pool(pool);
63 if let Some(id) = user_id {
64 builder = builder.as_user(id);
65 }
66 builder.build()
67 }
68
69 pub fn db(&self) -> Option<&PgPool> {
71 self.pool.as_ref()
72 }
73
74 pub fn require_user_id(&self) -> Result<Uuid> {
76 self.auth.require_user_id()
77 }
78
79 pub fn has_role(&self, role: &str) -> bool {
81 self.auth.has_role(role)
82 }
83
84 pub fn claim(&self, key: &str) -> Option<&serde_json::Value> {
86 self.auth.claim(key)
87 }
88
89 pub fn tenant_id(&self) -> Option<Uuid> {
91 self.tenant_id
92 }
93
94 pub fn env_mock(&self) -> &MockEnvProvider {
96 &self.env_provider
97 }
98}
99
100impl EnvAccess for TestQueryContext {
101 fn env_provider(&self) -> &dyn EnvProvider {
102 self.env_provider.as_ref()
103 }
104}
105
106#[derive(Default)]
108pub struct TestQueryContextBuilder {
109 user_id: Option<Uuid>,
110 roles: Vec<String>,
111 claims: HashMap<String, serde_json::Value>,
112 tenant_id: Option<Uuid>,
113 pool: Option<PgPool>,
114 env_vars: HashMap<String, String>,
115}
116
117impl TestQueryContextBuilder {
118 pub fn as_user(mut self, id: Uuid) -> Self {
120 self.user_id = Some(id);
121 self
122 }
123
124 pub fn with_role(mut self, role: impl Into<String>) -> Self {
126 self.roles.push(role.into());
127 self
128 }
129
130 pub fn with_roles(mut self, roles: Vec<String>) -> Self {
132 self.roles.extend(roles);
133 self
134 }
135
136 pub fn with_claim(mut self, key: impl Into<String>, value: serde_json::Value) -> Self {
138 self.claims.insert(key.into(), value);
139 self
140 }
141
142 pub fn with_tenant(mut self, tenant_id: Uuid) -> Self {
144 self.tenant_id = Some(tenant_id);
145 self
146 }
147
148 pub fn with_pool(mut self, pool: PgPool) -> Self {
150 self.pool = Some(pool);
151 self
152 }
153
154 pub fn with_env(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
156 self.env_vars.insert(key.into(), value.into());
157 self
158 }
159
160 pub fn with_envs(mut self, vars: HashMap<String, String>) -> Self {
162 self.env_vars.extend(vars);
163 self
164 }
165
166 pub fn build(self) -> TestQueryContext {
168 let auth = if let Some(user_id) = self.user_id {
169 AuthContext::authenticated(user_id, self.roles, self.claims)
170 } else {
171 AuthContext::unauthenticated()
172 };
173
174 TestQueryContext {
175 auth,
176 request: RequestMetadata::default(),
177 pool: self.pool,
178 tenant_id: self.tenant_id,
179 env_provider: Arc::new(MockEnvProvider::with_vars(self.env_vars)),
180 }
181 }
182}
183
184#[cfg(test)]
185mod tests {
186 use super::*;
187
188 #[test]
189 fn test_minimal_context() {
190 let ctx = TestQueryContext::minimal();
191 assert!(!ctx.auth.is_authenticated());
192 assert!(ctx.db().is_none());
193 }
194
195 #[test]
196 fn test_authenticated_context() {
197 let user_id = Uuid::new_v4();
198 let ctx = TestQueryContext::authenticated(user_id);
199 assert!(ctx.auth.is_authenticated());
200 assert_eq!(ctx.require_user_id().unwrap(), user_id);
201 }
202
203 #[test]
204 fn test_context_with_roles() {
205 let ctx = TestQueryContext::builder()
206 .as_user(Uuid::new_v4())
207 .with_role("admin")
208 .with_role("user")
209 .build();
210
211 assert!(ctx.has_role("admin"));
212 assert!(ctx.has_role("user"));
213 assert!(!ctx.has_role("superuser"));
214 }
215
216 #[test]
217 fn test_context_with_claims() {
218 let ctx = TestQueryContext::builder()
219 .as_user(Uuid::new_v4())
220 .with_claim("org_id", serde_json::json!("org-123"))
221 .build();
222
223 assert_eq!(ctx.claim("org_id"), Some(&serde_json::json!("org-123")));
224 assert!(ctx.claim("nonexistent").is_none());
225 }
226
227 #[test]
228 fn test_context_with_env() {
229 let ctx = TestQueryContext::builder()
230 .with_env("API_KEY", "test_key_123")
231 .with_env("TIMEOUT", "30")
232 .build();
233
234 assert_eq!(ctx.env("API_KEY"), Some("test_key_123".to_string()));
236 assert_eq!(ctx.env_or("TIMEOUT", "10"), "30");
237 assert_eq!(ctx.env_or("MISSING", "default"), "default");
238
239 assert!(ctx.env_require("API_KEY").is_ok());
241 assert!(ctx.env_require("MISSING").is_err());
242
243 let timeout: u32 = ctx.env_parse("TIMEOUT").unwrap();
245 assert_eq!(timeout, 30);
246
247 ctx.env_mock().assert_accessed("API_KEY");
249 ctx.env_mock().assert_accessed("TIMEOUT");
250 }
251}