Skip to main content

backbone_bucket/application/workflows/
file_upload_workflow.rs

1//! FileUpload flow implementation
2//!
3//! Generated by metaphor-schema
4//!
5//! Complete file upload workflow including:
6//! - Quota validation
7//! - File validation
8//! - Content deduplication
9//! - Virus scanning
10//! - Thumbnail generation (images and video)
11//! - Document preview generation
12//! - CDN URL generation
13//! - Quota update
14//! - Event emission
15
16use serde::{Deserialize, Serialize};
17use std::sync::Arc;
18use chrono;
19use backbone_core::flow::{WorkflowStep, WorkflowContext};
20
21/// Error type for flow execution
22#[derive(Debug, Clone, thiserror::Error)]
23pub enum FlowError {
24    #[error("No current step to execute")]
25    NoCurrentStep,
26
27    #[error("Step execution failed: {0}")]
28    StepFailed(String),
29
30    #[error("Condition evaluation failed: {0}")]
31    ConditionFailed(String),
32
33    #[error("Compensation failed: {0}")]
34    CompensationFailed(String),
35
36    #[error("Flow timed out")]
37    Timeout,
38
39    #[error("Flow cancelled")]
40    Cancelled,
41
42    #[error("Invalid state transition: {from} -> {to}")]
43    InvalidTransition { from: String, to: String },
44}
45
46/// Execution status for FileUpload flow
47#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
48#[serde(rename_all = "snake_case")]
49pub enum FileUploadFlowStatus {
50    /// Flow is pending execution
51    Pending,
52    /// Flow is currently running
53    Running,
54    /// Flow is waiting for an event or condition
55    Waiting,
56    /// Flow completed successfully
57    Completed,
58    /// Flow failed
59    Failed,
60    /// Flow was cancelled
61    Cancelled,
62    /// Flow is compensating (rolling back)
63    Compensating,
64}
65
66/// Steps in FileUpload flow
67#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
68#[serde(rename_all = "snake_case")]
69pub enum FileUploadFlowStep {
70    /// load_bucket
71    LoadBucket,
72    /// check_bucket_active
73    CheckBucketActive,
74    /// check_deduplication_enabled
75    CheckDeduplicationEnabled,
76    /// calculate_checksum
77    CalculateChecksum,
78    /// check_duplicate_content
79    CheckDuplicateContent,
80    /// use_existing_content
81    UseExistingContent,
82    /// detect_mime_type
83    DetectMimeType,
84    /// validate_file_type
85    ValidateFileType,
86    /// validate_file_size
87    ValidateFileSize,
88    /// load_quota
89    LoadQuota,
90    /// create_default_quota
91    CreateDefaultQuota,
92    /// check_quota
93    CheckQuota,
94    /// create_file_record
95    CreateFileRecord,
96    /// check_should_store_content
97    CheckShouldStoreContent,
98    /// store_content
99    StoreContent,
100    /// create_content_hash
101    CreateContentHash,
102    /// link_content_hash
103    LinkContentHash,
104    /// update_file_processing
105    UpdateFileProcessing,
106    /// scan_file
107    ScanFile,
108    /// check_scan_result
109    CheckScanResult,
110    /// update_scan_complete
111    UpdateScanComplete,
112    /// check_cdn_enabled
113    CheckCdnEnabled,
114    /// generate_cdn_url
115    GenerateCdnUrl,
116    /// update_cdn_info
117    UpdateCdnInfo,
118    /// mark_file_active
119    MarkFileActive,
120    /// update_quota
121    UpdateQuota,
122    /// update_bucket_stats
123    UpdateBucketStats,
124    /// emit_uploaded_event
125    EmitUploadedEvent,
126    /// complete
127    Complete,
128    /// quarantine_file
129    QuarantineFile,
130    /// emit_threat_event
131    EmitThreatEvent,
132    /// notify_threat
133    NotifyThreat,
134    /// complete_quarantine
135    CompleteQuarantine,
136    /// bucket_not_found
137    BucketNotFound,
138    /// bucket_not_active
139    BucketNotActive,
140    /// mime_type_not_allowed
141    MimeTypeNotAllowed,
142    /// file_too_large
143    FileTooLarge,
144    /// quota_exceeded
145    QuotaExceeded,
146    /// quota_exceeded_terminal
147    QuotaExceededTerminal,
148    /// rollback_file_record
149    RollbackFileRecord,
150    /// storage_failed
151    StorageFailed,
152}
153
154/// Instance of FileUpload flow execution
155#[derive(Debug, Clone, Serialize, Deserialize)]
156pub struct FileUploadFlowInstance {
157    /// Unique instance ID
158    pub id: String,
159    /// Current status
160    pub status: FileUploadFlowStatus,
161    /// Current step
162    pub current_step: Option<FileUploadFlowStep>,
163    /// Flow context (variables)
164    pub context: serde_json::Value,
165    /// Completed steps
166    pub completed_steps: Vec<FileUploadFlowStep>,
167    /// Error if failed
168    pub error: Option<String>,
169    /// Created timestamp
170    pub created_at: chrono::DateTime<chrono::Utc>,
171    /// Updated timestamp
172    pub updated_at: chrono::DateTime<chrono::Utc>,
173}
174
175impl FileUploadFlowInstance {
176    /// Create a new flow instance
177    pub fn new(id: impl Into<String>) -> Self {
178        let now = chrono::Utc::now();
179        Self {
180            id: id.into(),
181            status: FileUploadFlowStatus::Pending,
182            current_step: None,
183            context: serde_json::json!({}),
184            completed_steps: Vec::new(),
185            error: None,
186            created_at: now,
187            updated_at: now,
188        }
189    }
190
191    /// Check if flow is complete
192    pub fn is_complete(&self) -> bool {
193        matches!(
194            self.status,
195            FileUploadFlowStatus::Completed | FileUploadFlowStatus::Failed | FileUploadFlowStatus::Cancelled
196        )
197    }
198
199    /// Check if flow is running
200    pub fn is_running(&self) -> bool {
201        matches!(
202            self.status,
203            FileUploadFlowStatus::Running | FileUploadFlowStatus::Waiting
204        )
205    }
206
207    /// Set a context variable
208    pub fn set_context(&mut self, key: &str, value: serde_json::Value) {
209        if let serde_json::Value::Object(ref mut map) = self.context {
210            map.insert(key.to_string(), value);
211        }
212        self.updated_at = chrono::Utc::now();
213    }
214
215    /// Get a context variable
216    pub fn get_context(&self, key: &str) -> Option<&serde_json::Value> {
217        if let serde_json::Value::Object(ref map) = self.context {
218            map.get(key)
219        } else {
220            None
221        }
222    }
223}
224
225// Phase 1: FlowInstance satisfies backbone_core::flow::WorkflowContext.
226// The entity() accessor requires the entity to be carried in the instance;
227// add `pub entity: FileUpload` in the // <<< CUSTOM section and remove the todo!.
228#[allow(unused_variables)]
229impl WorkflowContext<FileUploadFlowInstance> for FileUploadFlowInstance {
230    fn entity(&self) -> &FileUploadFlowInstance { self }
231    fn set_var(&mut self, key: &str, value: serde_json::Value) {
232        self.set_context(key, value);
233    }
234    fn get_var(&self, key: &str) -> Option<&serde_json::Value> {
235        self.get_context(key)
236    }
237}
238
239/// Step handler trait for FileUpload flow
240#[async_trait::async_trait]
241pub trait FileUploadStepHandler: Send + Sync {
242    /// Handle load_bucket step
243    async fn handle_load_bucket(
244        &self,
245        instance: &mut FileUploadFlowInstance,
246    ) -> Result<Option<FileUploadFlowStep>, FlowError>;
247
248    /// Evaluate condition for check_bucket_active step
249    async fn evaluate_check_bucket_active(
250        &self,
251        instance: &FileUploadFlowInstance,
252    ) -> Result<FileUploadFlowStep, FlowError>;
253
254    /// Evaluate condition for check_deduplication_enabled step
255    async fn evaluate_check_deduplication_enabled(
256        &self,
257        instance: &FileUploadFlowInstance,
258    ) -> Result<FileUploadFlowStep, FlowError>;
259
260    /// Handle calculate_checksum step
261    async fn handle_calculate_checksum(
262        &self,
263        instance: &mut FileUploadFlowInstance,
264    ) -> Result<Option<FileUploadFlowStep>, FlowError>;
265
266    /// Handle check_duplicate_content step
267    async fn handle_check_duplicate_content(
268        &self,
269        instance: &mut FileUploadFlowInstance,
270    ) -> Result<Option<FileUploadFlowStep>, FlowError>;
271
272    /// Handle use_existing_content step
273    async fn handle_use_existing_content(
274        &self,
275        instance: &mut FileUploadFlowInstance,
276    ) -> Result<Option<FileUploadFlowStep>, FlowError>;
277
278    /// Handle detect_mime_type step
279    async fn handle_detect_mime_type(
280        &self,
281        instance: &mut FileUploadFlowInstance,
282    ) -> Result<Option<FileUploadFlowStep>, FlowError>;
283
284    /// Evaluate condition for validate_file_type step
285    async fn evaluate_validate_file_type(
286        &self,
287        instance: &FileUploadFlowInstance,
288    ) -> Result<FileUploadFlowStep, FlowError>;
289
290    /// Evaluate condition for validate_file_size step
291    async fn evaluate_validate_file_size(
292        &self,
293        instance: &FileUploadFlowInstance,
294    ) -> Result<FileUploadFlowStep, FlowError>;
295
296    /// Handle load_quota step
297    async fn handle_load_quota(
298        &self,
299        instance: &mut FileUploadFlowInstance,
300    ) -> Result<Option<FileUploadFlowStep>, FlowError>;
301
302    /// Handle create_default_quota step
303    async fn handle_create_default_quota(
304        &self,
305        instance: &mut FileUploadFlowInstance,
306    ) -> Result<Option<FileUploadFlowStep>, FlowError>;
307
308    /// Evaluate condition for check_quota step
309    async fn evaluate_check_quota(
310        &self,
311        instance: &FileUploadFlowInstance,
312    ) -> Result<FileUploadFlowStep, FlowError>;
313
314    /// Handle create_file_record step
315    async fn handle_create_file_record(
316        &self,
317        instance: &mut FileUploadFlowInstance,
318    ) -> Result<Option<FileUploadFlowStep>, FlowError>;
319
320    /// Evaluate condition for check_should_store_content step
321    async fn evaluate_check_should_store_content(
322        &self,
323        instance: &FileUploadFlowInstance,
324    ) -> Result<FileUploadFlowStep, FlowError>;
325
326    /// Handle store_content step
327    async fn handle_store_content(
328        &self,
329        instance: &mut FileUploadFlowInstance,
330    ) -> Result<Option<FileUploadFlowStep>, FlowError>;
331
332    /// Handle create_content_hash step
333    async fn handle_create_content_hash(
334        &self,
335        instance: &mut FileUploadFlowInstance,
336    ) -> Result<Option<FileUploadFlowStep>, FlowError>;
337
338    /// Handle link_content_hash step
339    async fn handle_link_content_hash(
340        &self,
341        instance: &mut FileUploadFlowInstance,
342    ) -> Result<Option<FileUploadFlowStep>, FlowError>;
343
344    /// Handle update_file_processing step
345    async fn handle_update_file_processing(
346        &self,
347        instance: &mut FileUploadFlowInstance,
348    ) -> Result<Option<FileUploadFlowStep>, FlowError>;
349
350    /// Handle scan_file step
351    async fn handle_scan_file(
352        &self,
353        instance: &mut FileUploadFlowInstance,
354    ) -> Result<Option<FileUploadFlowStep>, FlowError>;
355
356    /// Evaluate condition for check_scan_result step
357    async fn evaluate_check_scan_result(
358        &self,
359        instance: &FileUploadFlowInstance,
360    ) -> Result<FileUploadFlowStep, FlowError>;
361
362    /// Handle update_scan_complete step
363    async fn handle_update_scan_complete(
364        &self,
365        instance: &mut FileUploadFlowInstance,
366    ) -> Result<Option<FileUploadFlowStep>, FlowError>;
367
368    /// Evaluate condition for check_cdn_enabled step
369    async fn evaluate_check_cdn_enabled(
370        &self,
371        instance: &FileUploadFlowInstance,
372    ) -> Result<FileUploadFlowStep, FlowError>;
373
374    /// Handle generate_cdn_url step
375    async fn handle_generate_cdn_url(
376        &self,
377        instance: &mut FileUploadFlowInstance,
378    ) -> Result<Option<FileUploadFlowStep>, FlowError>;
379
380    /// Handle update_cdn_info step
381    async fn handle_update_cdn_info(
382        &self,
383        instance: &mut FileUploadFlowInstance,
384    ) -> Result<Option<FileUploadFlowStep>, FlowError>;
385
386    /// Handle mark_file_active step
387    async fn handle_mark_file_active(
388        &self,
389        instance: &mut FileUploadFlowInstance,
390    ) -> Result<Option<FileUploadFlowStep>, FlowError>;
391
392    /// Handle update_quota step
393    async fn handle_update_quota(
394        &self,
395        instance: &mut FileUploadFlowInstance,
396    ) -> Result<Option<FileUploadFlowStep>, FlowError>;
397
398    /// Handle update_bucket_stats step
399    async fn handle_update_bucket_stats(
400        &self,
401        instance: &mut FileUploadFlowInstance,
402    ) -> Result<Option<FileUploadFlowStep>, FlowError>;
403
404    /// Handle emit_uploaded_event step
405    async fn handle_emit_uploaded_event(
406        &self,
407        instance: &mut FileUploadFlowInstance,
408    ) -> Result<Option<FileUploadFlowStep>, FlowError>;
409
410    /// Handle terminal step complete
411    async fn handle_complete(
412        &self,
413        instance: &mut FileUploadFlowInstance,
414    ) -> Result<(), FlowError>;
415
416    /// Handle quarantine_file step
417    async fn handle_quarantine_file(
418        &self,
419        instance: &mut FileUploadFlowInstance,
420    ) -> Result<Option<FileUploadFlowStep>, FlowError>;
421
422    /// Handle emit_threat_event step
423    async fn handle_emit_threat_event(
424        &self,
425        instance: &mut FileUploadFlowInstance,
426    ) -> Result<Option<FileUploadFlowStep>, FlowError>;
427
428    /// Handle notify_threat step
429    async fn handle_notify_threat(
430        &self,
431        instance: &mut FileUploadFlowInstance,
432    ) -> Result<Option<FileUploadFlowStep>, FlowError>;
433
434    /// Handle terminal step complete_quarantine
435    async fn handle_complete_quarantine(
436        &self,
437        instance: &mut FileUploadFlowInstance,
438    ) -> Result<(), FlowError>;
439
440    /// Handle terminal step bucket_not_found
441    async fn handle_bucket_not_found(
442        &self,
443        instance: &mut FileUploadFlowInstance,
444    ) -> Result<(), FlowError>;
445
446    /// Handle terminal step bucket_not_active
447    async fn handle_bucket_not_active(
448        &self,
449        instance: &mut FileUploadFlowInstance,
450    ) -> Result<(), FlowError>;
451
452    /// Handle terminal step mime_type_not_allowed
453    async fn handle_mime_type_not_allowed(
454        &self,
455        instance: &mut FileUploadFlowInstance,
456    ) -> Result<(), FlowError>;
457
458    /// Handle terminal step file_too_large
459    async fn handle_file_too_large(
460        &self,
461        instance: &mut FileUploadFlowInstance,
462    ) -> Result<(), FlowError>;
463
464    /// Handle quota_exceeded step
465    async fn handle_quota_exceeded(
466        &self,
467        instance: &mut FileUploadFlowInstance,
468    ) -> Result<Option<FileUploadFlowStep>, FlowError>;
469
470    /// Handle terminal step quota_exceeded_terminal
471    async fn handle_quota_exceeded_terminal(
472        &self,
473        instance: &mut FileUploadFlowInstance,
474    ) -> Result<(), FlowError>;
475
476    /// Handle rollback_file_record step
477    async fn handle_rollback_file_record(
478        &self,
479        instance: &mut FileUploadFlowInstance,
480    ) -> Result<Option<FileUploadFlowStep>, FlowError>;
481
482    /// Handle terminal step storage_failed
483    async fn handle_storage_failed(
484        &self,
485        instance: &mut FileUploadFlowInstance,
486    ) -> Result<(), FlowError>;
487
488    /// Execute compensation (rollback)
489    async fn compensate(
490        &self,
491        instance: &mut FileUploadFlowInstance,
492    ) -> Result<(), FlowError>;
493
494}
495
496/// Executor for FileUpload flow
497pub struct FileUploadFlowExecutor<H: FileUploadStepHandler> {
498    handler: Arc<H>,
499}
500
501impl<H: FileUploadStepHandler> FileUploadFlowExecutor<H> {
502    /// Create a new flow executor
503    pub fn new(handler: Arc<H>) -> Self {
504        Self { handler }
505    }
506
507    /// Start a new flow instance
508    pub async fn start(&self, instance_id: impl Into<String>) -> Result<FileUploadFlowInstance, FlowError> {
509        let mut instance = FileUploadFlowInstance::new(instance_id);
510        instance.status = FileUploadFlowStatus::Running;
511        instance.current_step = Some(FileUploadFlowStep::LoadBucket);
512        Ok(instance)
513    }
514
515    /// Execute the current step
516    pub async fn execute_step(&self, instance: &mut FileUploadFlowInstance) -> Result<(), FlowError> {
517        let current_step = match instance.current_step {
518            Some(step) => step,
519            None => return Err(FlowError::NoCurrentStep),
520        };
521
522        let next_step = match current_step {
523            FileUploadFlowStep::LoadBucket => {
524                self.handler.handle_load_bucket(instance).await?
525            }
526            FileUploadFlowStep::CheckBucketActive => {
527                let next = self.handler.evaluate_check_bucket_active(instance).await?;
528                Some(next)
529            }
530            FileUploadFlowStep::CheckDeduplicationEnabled => {
531                let next = self.handler.evaluate_check_deduplication_enabled(instance).await?;
532                Some(next)
533            }
534            FileUploadFlowStep::CalculateChecksum => {
535                self.handler.handle_calculate_checksum(instance).await?
536            }
537            FileUploadFlowStep::CheckDuplicateContent => {
538                self.handler.handle_check_duplicate_content(instance).await?
539            }
540            FileUploadFlowStep::UseExistingContent => {
541                self.handler.handle_use_existing_content(instance).await?
542            }
543            FileUploadFlowStep::DetectMimeType => {
544                self.handler.handle_detect_mime_type(instance).await?
545            }
546            FileUploadFlowStep::ValidateFileType => {
547                let next = self.handler.evaluate_validate_file_type(instance).await?;
548                Some(next)
549            }
550            FileUploadFlowStep::ValidateFileSize => {
551                let next = self.handler.evaluate_validate_file_size(instance).await?;
552                Some(next)
553            }
554            FileUploadFlowStep::LoadQuota => {
555                self.handler.handle_load_quota(instance).await?
556            }
557            FileUploadFlowStep::CreateDefaultQuota => {
558                self.handler.handle_create_default_quota(instance).await?
559            }
560            FileUploadFlowStep::CheckQuota => {
561                let next = self.handler.evaluate_check_quota(instance).await?;
562                Some(next)
563            }
564            FileUploadFlowStep::CreateFileRecord => {
565                self.handler.handle_create_file_record(instance).await?
566            }
567            FileUploadFlowStep::CheckShouldStoreContent => {
568                let next = self.handler.evaluate_check_should_store_content(instance).await?;
569                Some(next)
570            }
571            FileUploadFlowStep::StoreContent => {
572                self.handler.handle_store_content(instance).await?
573            }
574            FileUploadFlowStep::CreateContentHash => {
575                self.handler.handle_create_content_hash(instance).await?
576            }
577            FileUploadFlowStep::LinkContentHash => {
578                self.handler.handle_link_content_hash(instance).await?
579            }
580            FileUploadFlowStep::UpdateFileProcessing => {
581                self.handler.handle_update_file_processing(instance).await?
582            }
583            FileUploadFlowStep::ScanFile => {
584                self.handler.handle_scan_file(instance).await?
585            }
586            FileUploadFlowStep::CheckScanResult => {
587                let next = self.handler.evaluate_check_scan_result(instance).await?;
588                Some(next)
589            }
590            FileUploadFlowStep::UpdateScanComplete => {
591                self.handler.handle_update_scan_complete(instance).await?
592            }
593            FileUploadFlowStep::CheckCdnEnabled => {
594                let next = self.handler.evaluate_check_cdn_enabled(instance).await?;
595                Some(next)
596            }
597            FileUploadFlowStep::GenerateCdnUrl => {
598                self.handler.handle_generate_cdn_url(instance).await?
599            }
600            FileUploadFlowStep::UpdateCdnInfo => {
601                self.handler.handle_update_cdn_info(instance).await?
602            }
603            FileUploadFlowStep::MarkFileActive => {
604                self.handler.handle_mark_file_active(instance).await?
605            }
606            FileUploadFlowStep::UpdateQuota => {
607                self.handler.handle_update_quota(instance).await?
608            }
609            FileUploadFlowStep::UpdateBucketStats => {
610                self.handler.handle_update_bucket_stats(instance).await?
611            }
612            FileUploadFlowStep::EmitUploadedEvent => {
613                self.handler.handle_emit_uploaded_event(instance).await?
614            }
615            FileUploadFlowStep::Complete => {
616                self.handler.handle_complete(instance).await?;
617                None // Terminal step
618            }
619            FileUploadFlowStep::QuarantineFile => {
620                self.handler.handle_quarantine_file(instance).await?
621            }
622            FileUploadFlowStep::EmitThreatEvent => {
623                self.handler.handle_emit_threat_event(instance).await?
624            }
625            FileUploadFlowStep::NotifyThreat => {
626                self.handler.handle_notify_threat(instance).await?
627            }
628            FileUploadFlowStep::CompleteQuarantine => {
629                self.handler.handle_complete_quarantine(instance).await?;
630                None // Terminal step
631            }
632            FileUploadFlowStep::BucketNotFound => {
633                self.handler.handle_bucket_not_found(instance).await?;
634                None // Terminal step
635            }
636            FileUploadFlowStep::BucketNotActive => {
637                self.handler.handle_bucket_not_active(instance).await?;
638                None // Terminal step
639            }
640            FileUploadFlowStep::MimeTypeNotAllowed => {
641                self.handler.handle_mime_type_not_allowed(instance).await?;
642                None // Terminal step
643            }
644            FileUploadFlowStep::FileTooLarge => {
645                self.handler.handle_file_too_large(instance).await?;
646                None // Terminal step
647            }
648            FileUploadFlowStep::QuotaExceeded => {
649                self.handler.handle_quota_exceeded(instance).await?
650            }
651            FileUploadFlowStep::QuotaExceededTerminal => {
652                self.handler.handle_quota_exceeded_terminal(instance).await?;
653                None // Terminal step
654            }
655            FileUploadFlowStep::RollbackFileRecord => {
656                self.handler.handle_rollback_file_record(instance).await?
657            }
658            FileUploadFlowStep::StorageFailed => {
659                self.handler.handle_storage_failed(instance).await?;
660                None // Terminal step
661            }
662        };
663
664        // Mark current step as completed
665        instance.completed_steps.push(current_step);
666
667        // Move to next step
668        match next_step {
669            Some(next) => {
670                instance.current_step = Some(next);
671            }
672            None => {
673                instance.current_step = None;
674                instance.status = FileUploadFlowStatus::Completed;
675            }
676        }
677
678        instance.updated_at = chrono::Utc::now();
679        Ok(())
680    }
681
682    /// Run the flow to completion
683    pub async fn run(&self, instance: &mut FileUploadFlowInstance) -> Result<(), FlowError> {
684        while !instance.is_complete() && instance.status != FileUploadFlowStatus::Waiting {
685            self.execute_step(instance).await?;
686        }
687        Ok(())
688    }
689
690    /// Cancel the flow
691    pub fn cancel(&self, instance: &mut FileUploadFlowInstance) {
692        instance.status = FileUploadFlowStatus::Cancelled;
693        instance.updated_at = chrono::Utc::now();
694    }
695
696    /// Mark flow as failed
697    pub fn fail(&self, instance: &mut FileUploadFlowInstance, error: impl Into<String>) {
698        instance.status = FileUploadFlowStatus::Failed;
699        instance.error = Some(error.into());
700        instance.updated_at = chrono::Utc::now();
701    }
702}
703
704#[cfg(test)]
705mod tests {
706    use super::*;
707
708    #[test]
709    fn test_flow_instance_creation() {
710        let instance = FileUploadFlowInstance::new("test-1");
711        assert_eq!(instance.id, "test-1");
712        assert_eq!(instance.status, FileUploadFlowStatus::Pending);
713        assert!(!instance.is_complete());
714    }
715
716    #[test]
717    fn test_flow_context() {
718        let mut instance = FileUploadFlowInstance::new("test-2");
719        instance.set_context("key", serde_json::json!("value"));
720        let value = instance.get_context("key");
721        assert_eq!(value, Some(&serde_json::json!("value")));
722    }
723
724    #[test]
725    fn test_flow_status_transitions() {
726        let mut instance = FileUploadFlowInstance::new("test-3");
727        assert!(!instance.is_running());
728
729        instance.status = FileUploadFlowStatus::Running;
730        assert!(instance.is_running());
731        assert!(!instance.is_complete());
732
733        instance.status = FileUploadFlowStatus::Completed;
734        assert!(instance.is_complete());
735        assert!(!instance.is_running());
736    }
737}