forge_core/testing/context/
action.rs1use std::collections::HashMap;
4use std::sync::Arc;
5
6use sqlx::PgPool;
7use uuid::Uuid;
8
9use super::super::mock_dispatch::{MockJobDispatch, MockWorkflowDispatch};
10use super::super::mock_http::{MockHttp, MockRequest, MockResponse};
11use crate::Result;
12use crate::env::{EnvAccess, EnvProvider, MockEnvProvider};
13use crate::function::{AuthContext, RequestMetadata};
14
15pub struct TestActionContext {
35 pub auth: AuthContext,
37 pub request: RequestMetadata,
39 pool: Option<PgPool>,
41 http: Arc<MockHttp>,
43 job_dispatch: Arc<MockJobDispatch>,
45 workflow_dispatch: Arc<MockWorkflowDispatch>,
47 env_provider: Arc<MockEnvProvider>,
49}
50
51impl TestActionContext {
52 pub fn builder() -> TestActionContextBuilder {
54 TestActionContextBuilder::default()
55 }
56
57 pub fn minimal() -> Self {
59 Self::builder().build()
60 }
61
62 pub fn authenticated(user_id: Uuid) -> Self {
64 Self::builder().as_user(user_id).build()
65 }
66
67 pub fn db(&self) -> Option<&PgPool> {
69 self.pool.as_ref()
70 }
71
72 pub fn http(&self) -> &MockHttp {
74 &self.http
75 }
76
77 pub fn job_dispatch(&self) -> &MockJobDispatch {
79 &self.job_dispatch
80 }
81
82 pub fn workflow_dispatch(&self) -> &MockWorkflowDispatch {
84 &self.workflow_dispatch
85 }
86
87 pub fn require_user_id(&self) -> Result<Uuid> {
89 self.auth.require_user_id()
90 }
91
92 pub async fn dispatch_job<T: serde::Serialize>(&self, job_type: &str, args: T) -> Result<Uuid> {
94 self.job_dispatch.dispatch(job_type, args).await
95 }
96
97 pub async fn start_workflow<T: serde::Serialize>(&self, name: &str, input: T) -> Result<Uuid> {
99 self.workflow_dispatch.start(name, input).await
100 }
101
102 pub fn env_mock(&self) -> &MockEnvProvider {
104 &self.env_provider
105 }
106}
107
108impl EnvAccess for TestActionContext {
109 fn env_provider(&self) -> &dyn EnvProvider {
110 self.env_provider.as_ref()
111 }
112}
113
114pub struct TestActionContextBuilder {
116 user_id: Option<Uuid>,
117 roles: Vec<String>,
118 claims: HashMap<String, serde_json::Value>,
119 pool: Option<PgPool>,
120 http: MockHttp,
121 job_dispatch: Arc<MockJobDispatch>,
122 workflow_dispatch: Arc<MockWorkflowDispatch>,
123 env_vars: HashMap<String, String>,
124}
125
126impl Default for TestActionContextBuilder {
127 fn default() -> Self {
128 Self {
129 user_id: None,
130 roles: Vec::new(),
131 claims: HashMap::new(),
132 pool: None,
133 http: MockHttp::new(),
134 job_dispatch: Arc::new(MockJobDispatch::new()),
135 workflow_dispatch: Arc::new(MockWorkflowDispatch::new()),
136 env_vars: HashMap::new(),
137 }
138 }
139}
140
141impl TestActionContextBuilder {
142 pub fn as_user(mut self, id: Uuid) -> Self {
144 self.user_id = Some(id);
145 self
146 }
147
148 pub fn with_role(mut self, role: impl Into<String>) -> Self {
150 self.roles.push(role.into());
151 self
152 }
153
154 pub fn with_roles(mut self, roles: Vec<String>) -> Self {
156 self.roles.extend(roles);
157 self
158 }
159
160 pub fn with_claim(mut self, key: impl Into<String>, value: serde_json::Value) -> Self {
162 self.claims.insert(key.into(), value);
163 self
164 }
165
166 pub fn with_pool(mut self, pool: PgPool) -> Self {
168 self.pool = Some(pool);
169 self
170 }
171
172 pub fn mock_http<F>(self, pattern: &str, handler: F) -> Self
174 where
175 F: Fn(&MockRequest) -> MockResponse + Send + Sync + 'static,
176 {
177 self.http.add_mock_sync(pattern, handler);
178 self
179 }
180
181 pub fn mock_http_json<T: serde::Serialize>(self, pattern: &str, response: T) -> Self {
183 let json = serde_json::to_value(response).unwrap_or(serde_json::Value::Null);
184 self.mock_http(pattern, move |_| MockResponse::json(json.clone()))
185 }
186
187 pub fn with_env(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
189 self.env_vars.insert(key.into(), value.into());
190 self
191 }
192
193 pub fn with_envs(mut self, vars: HashMap<String, String>) -> Self {
195 self.env_vars.extend(vars);
196 self
197 }
198
199 pub fn build(self) -> TestActionContext {
201 let auth = if let Some(user_id) = self.user_id {
202 AuthContext::authenticated(user_id, self.roles, self.claims)
203 } else {
204 AuthContext::unauthenticated()
205 };
206
207 TestActionContext {
208 auth,
209 request: RequestMetadata::default(),
210 pool: self.pool,
211 http: Arc::new(self.http),
212 job_dispatch: self.job_dispatch,
213 workflow_dispatch: self.workflow_dispatch,
214 env_provider: Arc::new(MockEnvProvider::with_vars(self.env_vars)),
215 }
216 }
217}
218
219#[cfg(test)]
220mod tests {
221 use super::*;
222
223 #[test]
224 fn test_minimal_context() {
225 let ctx = TestActionContext::minimal();
226 assert!(!ctx.auth.is_authenticated());
227 }
228
229 #[test]
230 fn test_authenticated_context() {
231 let user_id = Uuid::new_v4();
232 let ctx = TestActionContext::authenticated(user_id);
233 assert!(ctx.auth.is_authenticated());
234 assert_eq!(ctx.require_user_id().unwrap(), user_id);
235 }
236}