durable_execution_sdk_testing/
error.rs1use durable_execution_sdk::{DurableError, OperationType};
7use thiserror::Error;
8
9use crate::types::WaitingOperationStatus;
10
11#[derive(Debug, Error)]
33pub enum TestError {
34 #[error("Handler execution failed: {0}")]
36 ExecutionFailed(#[from] DurableError),
37
38 #[error("Operation not found: {0}")]
40 OperationNotFound(String),
41
42 #[error("Operation type mismatch: expected {expected}, found {found}")]
44 OperationTypeMismatch {
45 expected: OperationType,
47 found: OperationType,
49 },
50
51 #[error("Not a callback operation")]
53 NotCallbackOperation,
54
55 #[error("Function not registered: {0}")]
57 FunctionNotRegistered(String),
58
59 #[error("Timeout waiting for operation: {0}")]
61 WaitTimeout(String),
62
63 #[error("Execution completed before operation {0} reached state {1}")]
65 ExecutionCompletedEarly(String, WaitingOperationStatus),
66
67 #[error("Serialization error: {0}")]
69 SerializationError(#[from] serde_json::Error),
70
71 #[error("AWS SDK error: {0}")]
73 AwsError(String),
74
75 #[error("Test environment not set up. Call setup_test_environment() first.")]
77 EnvironmentNotSetUp,
78
79 #[error("Invalid configuration: {0}")]
81 InvalidConfiguration(String),
82
83 #[error("Result not available: {0}")]
85 ResultNotAvailable(String),
86
87 #[error("Invalid checkpoint token: {0}")]
89 InvalidCheckpointToken(String),
90
91 #[error("Checkpoint server error: {0}")]
93 CheckpointServerError(String),
94
95 #[error("Checkpoint communication error: {0}")]
97 CheckpointCommunicationError(String),
98
99 #[error("Execution not found: {0}")]
101 ExecutionNotFound(String),
102
103 #[error("Invocation not found: {0}")]
105 InvocationNotFound(String),
106
107 #[error("Callback already completed: {0}")]
109 CallbackAlreadyCompleted(String),
110
111 #[error("Callback not found: {0}")]
113 CallbackNotFound(String),
114}
115
116impl TestError {
117 pub fn operation_not_found(name: impl Into<String>) -> Self {
119 Self::OperationNotFound(name.into())
120 }
121
122 pub fn type_mismatch(expected: OperationType, found: OperationType) -> Self {
124 Self::OperationTypeMismatch { expected, found }
125 }
126
127 pub fn function_not_registered(name: impl Into<String>) -> Self {
129 Self::FunctionNotRegistered(name.into())
130 }
131
132 pub fn wait_timeout(operation: impl Into<String>) -> Self {
134 Self::WaitTimeout(operation.into())
135 }
136
137 pub fn execution_completed_early(
139 operation: impl Into<String>,
140 status: WaitingOperationStatus,
141 ) -> Self {
142 Self::ExecutionCompletedEarly(operation.into(), status)
143 }
144
145 pub fn aws_error(message: impl Into<String>) -> Self {
147 Self::AwsError(message.into())
148 }
149
150 pub fn invalid_configuration(message: impl Into<String>) -> Self {
152 Self::InvalidConfiguration(message.into())
153 }
154
155 pub fn result_not_available(message: impl Into<String>) -> Self {
157 Self::ResultNotAvailable(message.into())
158 }
159
160 pub fn invalid_checkpoint_token(message: impl Into<String>) -> Self {
162 Self::InvalidCheckpointToken(message.into())
163 }
164
165 pub fn checkpoint_server_error(message: impl Into<String>) -> Self {
167 Self::CheckpointServerError(message.into())
168 }
169
170 pub fn checkpoint_communication_error(message: impl Into<String>) -> Self {
172 Self::CheckpointCommunicationError(message.into())
173 }
174
175 pub fn execution_not_found(message: impl Into<String>) -> Self {
177 Self::ExecutionNotFound(message.into())
178 }
179
180 pub fn invocation_not_found(message: impl Into<String>) -> Self {
182 Self::InvocationNotFound(message.into())
183 }
184
185 pub fn callback_already_completed(message: impl Into<String>) -> Self {
187 Self::CallbackAlreadyCompleted(message.into())
188 }
189
190 pub fn callback_not_found(message: impl Into<String>) -> Self {
192 Self::CallbackNotFound(message.into())
193 }
194
195 pub fn is_retriable(&self) -> bool {
197 match self {
198 Self::ExecutionFailed(e) => e.is_retriable(),
199 Self::WaitTimeout(_) => true,
200 _ => false,
201 }
202 }
203}
204
205pub type TestResult<T> = Result<T, TestError>;
207
208#[cfg(test)]
209mod tests {
210 use super::*;
211
212 #[test]
213 fn test_operation_not_found() {
214 let err = TestError::operation_not_found("my-step");
215 assert!(matches!(err, TestError::OperationNotFound(_)));
216 assert!(err.to_string().contains("my-step"));
217 }
218
219 #[test]
220 fn test_type_mismatch() {
221 let err = TestError::type_mismatch(OperationType::Step, OperationType::Wait);
222 assert!(matches!(err, TestError::OperationTypeMismatch { .. }));
223 assert!(err.to_string().contains("Step"));
224 assert!(err.to_string().contains("Wait"));
225 }
226
227 #[test]
228 fn test_not_callback_operation() {
229 let err = TestError::NotCallbackOperation;
230 assert!(err.to_string().contains("callback"));
231 }
232
233 #[test]
234 fn test_function_not_registered() {
235 let err = TestError::function_not_registered("my-function");
236 assert!(matches!(err, TestError::FunctionNotRegistered(_)));
237 assert!(err.to_string().contains("my-function"));
238 }
239
240 #[test]
241 fn test_wait_timeout() {
242 let err = TestError::wait_timeout("my-operation");
243 assert!(matches!(err, TestError::WaitTimeout(_)));
244 assert!(err.to_string().contains("my-operation"));
245 }
246
247 #[test]
248 fn test_execution_completed_early() {
249 let err =
250 TestError::execution_completed_early("my-operation", WaitingOperationStatus::Completed);
251 assert!(matches!(err, TestError::ExecutionCompletedEarly(_, _)));
252 assert!(err.to_string().contains("my-operation"));
253 assert!(err.to_string().contains("Completed"));
254 }
255
256 #[test]
257 fn test_aws_error() {
258 let err = TestError::aws_error("Connection failed");
259 assert!(matches!(err, TestError::AwsError(_)));
260 assert!(err.to_string().contains("Connection failed"));
261 }
262
263 #[test]
264 fn test_environment_not_set_up() {
265 let err = TestError::EnvironmentNotSetUp;
266 assert!(err.to_string().contains("setup_test_environment"));
267 }
268
269 #[test]
270 fn test_from_durable_error() {
271 let durable_err = DurableError::validation("Invalid input");
272 let test_err: TestError = durable_err.into();
273 assert!(matches!(test_err, TestError::ExecutionFailed(_)));
274 }
275
276 #[test]
277 fn test_from_serde_error() {
278 let json_err = serde_json::from_str::<String>("invalid").unwrap_err();
279 let test_err: TestError = json_err.into();
280 assert!(matches!(test_err, TestError::SerializationError(_)));
281 }
282
283 #[test]
284 fn test_is_retriable() {
285 let timeout_err = TestError::wait_timeout("op");
286 assert!(timeout_err.is_retriable());
287
288 let not_found_err = TestError::operation_not_found("op");
289 assert!(!not_found_err.is_retriable());
290
291 let retriable_durable =
292 TestError::ExecutionFailed(DurableError::checkpoint_retriable("temp error"));
293 assert!(retriable_durable.is_retriable());
294
295 let non_retriable_durable =
296 TestError::ExecutionFailed(DurableError::validation("bad input"));
297 assert!(!non_retriable_durable.is_retriable());
298 }
299}