forge_core/testing/context/
mutation.rs1use std::collections::HashMap;
4use std::sync::Arc;
5
6use sqlx::PgPool;
7use uuid::Uuid;
8
9use super::super::mock_dispatch::{MockJobDispatch, MockWorkflowDispatch};
10use crate::Result;
11use crate::env::{EnvAccess, EnvProvider, MockEnvProvider};
12use crate::function::{AuthContext, RequestMetadata};
13
14pub struct TestMutationContext {
33 pub auth: AuthContext,
35 pub request: RequestMetadata,
37 pool: Option<PgPool>,
39 job_dispatch: Arc<MockJobDispatch>,
41 workflow_dispatch: Arc<MockWorkflowDispatch>,
43 env_provider: Arc<MockEnvProvider>,
45}
46
47impl TestMutationContext {
48 pub fn builder() -> TestMutationContextBuilder {
50 TestMutationContextBuilder::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 db(&self) -> Option<&PgPool> {
65 self.pool.as_ref()
66 }
67
68 pub fn job_dispatch(&self) -> &MockJobDispatch {
70 &self.job_dispatch
71 }
72
73 pub fn workflow_dispatch(&self) -> &MockWorkflowDispatch {
75 &self.workflow_dispatch
76 }
77
78 pub fn require_user_id(&self) -> Result<Uuid> {
80 self.auth.require_user_id()
81 }
82
83 pub async fn dispatch_job<T: serde::Serialize>(&self, job_type: &str, args: T) -> Result<Uuid> {
85 self.job_dispatch.dispatch(job_type, args).await
86 }
87
88 pub async fn start_workflow<T: serde::Serialize>(&self, name: &str, input: T) -> Result<Uuid> {
90 self.workflow_dispatch.start(name, input).await
91 }
92
93 pub fn env_mock(&self) -> &MockEnvProvider {
95 &self.env_provider
96 }
97}
98
99impl EnvAccess for TestMutationContext {
100 fn env_provider(&self) -> &dyn EnvProvider {
101 self.env_provider.as_ref()
102 }
103}
104
105pub struct TestMutationContextBuilder {
107 user_id: Option<Uuid>,
108 roles: Vec<String>,
109 claims: HashMap<String, serde_json::Value>,
110 pool: Option<PgPool>,
111 job_dispatch: Arc<MockJobDispatch>,
112 workflow_dispatch: Arc<MockWorkflowDispatch>,
113 env_vars: HashMap<String, String>,
114}
115
116impl Default for TestMutationContextBuilder {
117 fn default() -> Self {
118 Self {
119 user_id: None,
120 roles: Vec::new(),
121 claims: HashMap::new(),
122 pool: None,
123 job_dispatch: Arc::new(MockJobDispatch::new()),
124 workflow_dispatch: Arc::new(MockWorkflowDispatch::new()),
125 env_vars: HashMap::new(),
126 }
127 }
128}
129
130impl TestMutationContextBuilder {
131 pub fn as_user(mut self, id: Uuid) -> Self {
133 self.user_id = Some(id);
134 self
135 }
136
137 pub fn with_role(mut self, role: impl Into<String>) -> Self {
139 self.roles.push(role.into());
140 self
141 }
142
143 pub fn with_roles(mut self, roles: Vec<String>) -> Self {
145 self.roles.extend(roles);
146 self
147 }
148
149 pub fn with_claim(mut self, key: impl Into<String>, value: serde_json::Value) -> Self {
151 self.claims.insert(key.into(), value);
152 self
153 }
154
155 pub fn with_pool(mut self, pool: PgPool) -> Self {
157 self.pool = Some(pool);
158 self
159 }
160
161 pub fn with_job_dispatch(mut self, dispatch: Arc<MockJobDispatch>) -> Self {
163 self.job_dispatch = dispatch;
164 self
165 }
166
167 pub fn with_workflow_dispatch(mut self, dispatch: Arc<MockWorkflowDispatch>) -> Self {
169 self.workflow_dispatch = dispatch;
170 self
171 }
172
173 pub fn with_env(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
175 self.env_vars.insert(key.into(), value.into());
176 self
177 }
178
179 pub fn with_envs(mut self, vars: HashMap<String, String>) -> Self {
181 self.env_vars.extend(vars);
182 self
183 }
184
185 pub fn build(self) -> TestMutationContext {
187 let auth = if let Some(user_id) = self.user_id {
188 AuthContext::authenticated(user_id, self.roles, self.claims)
189 } else {
190 AuthContext::unauthenticated()
191 };
192
193 TestMutationContext {
194 auth,
195 request: RequestMetadata::default(),
196 pool: self.pool,
197 job_dispatch: self.job_dispatch,
198 workflow_dispatch: self.workflow_dispatch,
199 env_provider: Arc::new(MockEnvProvider::with_vars(self.env_vars)),
200 }
201 }
202}
203
204#[cfg(test)]
205mod tests {
206 use super::*;
207
208 #[tokio::test]
209 async fn test_dispatch_job() {
210 let ctx = TestMutationContext::authenticated(Uuid::new_v4());
211
212 let job_id = ctx
213 .dispatch_job("send_email", serde_json::json!({"to": "test@example.com"}))
214 .await
215 .unwrap();
216
217 assert!(!job_id.is_nil());
218 ctx.job_dispatch().assert_dispatched("send_email");
219 }
220
221 #[tokio::test]
222 async fn test_start_workflow() {
223 let ctx = TestMutationContext::authenticated(Uuid::new_v4());
224
225 let run_id = ctx
226 .start_workflow("onboarding", serde_json::json!({"user_id": "123"}))
227 .await
228 .unwrap();
229
230 assert!(!run_id.is_nil());
231 ctx.workflow_dispatch().assert_started("onboarding");
232 }
233
234 #[tokio::test]
235 async fn test_job_not_dispatched() {
236 let ctx = TestMutationContext::authenticated(Uuid::new_v4());
237
238 ctx.job_dispatch().assert_not_dispatched("send_email");
239 }
240}