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