1use crate::error::DeployError;
8use crate::state::{DeploymentState, Step};
9use crate::traits::AkashBackend;
10use crate::types::{Bid, BidId};
11
12#[derive(Debug, Clone)]
14pub struct WorkflowConfig {
15 pub min_balance_uakt: u64,
17 pub bid_wait_seconds: u64,
19 pub max_bid_wait_attempts: u32,
21 pub max_endpoint_wait_attempts: u32,
23 pub auto_select_cheapest_bid: bool,
25 pub trusted_providers: Vec<String>,
27}
28
29impl Default for WorkflowConfig {
30 fn default() -> Self {
31 Self {
32 min_balance_uakt: 5_000_000, bid_wait_seconds: 12, max_bid_wait_attempts: 10,
35 max_endpoint_wait_attempts: 30,
36 auto_select_cheapest_bid: false,
37 trusted_providers: Vec::new(),
38 }
39 }
40}
41
42#[derive(Debug)]
44pub enum StepResult {
45 Continue,
47 NeedsInput(InputRequired),
49 Complete,
51 Failed(String),
53}
54
55#[derive(Debug)]
57pub enum InputRequired {
58 SelectProvider { bids: Vec<Bid> },
60 ProvideSdl,
62}
63
64pub struct DeploymentWorkflow<'a, B: AkashBackend> {
68 backend: &'a B,
69 signer: &'a B::Signer,
70 config: WorkflowConfig,
71}
72
73impl<'a, B: AkashBackend> DeploymentWorkflow<'a, B> {
74 pub fn new(backend: &'a B, signer: &'a B::Signer, config: WorkflowConfig) -> Self {
76 Self {
77 backend,
78 signer,
79 config,
80 }
81 }
82
83 pub async fn advance(&self, state: &mut DeploymentState) -> Result<StepResult, DeployError> {
88 let result = match &state.step {
89 Step::Init => self.step_init(state).await,
90 Step::CheckBalance => self.step_check_balance(state).await,
91 Step::EnsureCertificate => self.step_ensure_certificate(state).await,
92 Step::CreateDeployment => self.step_create_deployment(state).await,
93 Step::WaitForBids { waited_blocks } => {
94 self.step_wait_for_bids(state, *waited_blocks).await
95 }
96 Step::SelectProvider => self.step_select_provider(state).await,
97 Step::CreateLease => self.step_create_lease(state).await,
98 Step::SendManifest => self.step_send_manifest(state).await,
99 Step::WaitForEndpoints { attempts } => {
100 self.step_wait_for_endpoints(state, *attempts).await
101 }
102 Step::Complete => return Ok(StepResult::Complete),
103 Step::Failed { reason, .. } => return Ok(StepResult::Failed(reason.clone())),
104 };
105
106 self.backend.save_state(&state.session_id, state).await?;
108
109 result
110 }
111
112 pub async fn run_to_completion(
114 &self,
115 state: &mut DeploymentState,
116 ) -> Result<StepResult, DeployError> {
117 loop {
118 match self.advance(state).await? {
119 StepResult::Continue => continue,
120 other => return Ok(other),
121 }
122 }
123 }
124
125 async fn step_init(&self, state: &mut DeploymentState) -> Result<StepResult, DeployError> {
130 if state.sdl_content.is_none() {
132 return Ok(StepResult::NeedsInput(InputRequired::ProvideSdl));
133 }
134
135 state.transition(Step::CheckBalance);
136 Ok(StepResult::Continue)
137 }
138
139 async fn step_check_balance(
140 &self,
141 state: &mut DeploymentState,
142 ) -> Result<StepResult, DeployError> {
143 let balance = self.backend.query_balance(&state.owner, "uakt").await?;
144
145 if balance < self.config.min_balance_uakt as u128 {
146 state.fail(
147 format!(
148 "insufficient balance: {} uakt < {} uakt required",
149 balance, self.config.min_balance_uakt
150 ),
151 false, );
153 return Ok(StepResult::Failed(format!(
154 "insufficient balance: {}",
155 balance
156 )));
157 }
158
159 state.transition(Step::EnsureCertificate);
160 Ok(StepResult::Continue)
161 }
162
163 async fn step_ensure_certificate(
164 &self,
165 state: &mut DeploymentState,
166 ) -> Result<StepResult, DeployError> {
167 let cert = self.backend.query_certificate(&state.owner).await?;
169
170 if let Some(cert_info) = cert {
171 let key = self.backend.load_cert_key(&state.owner).await?;
173 if let Some(key_pem) = key {
174 state.cert_pem = Some(cert_info.cert_pem);
175 state.key_pem = Some(key_pem);
176 state.transition(Step::CreateDeployment);
177 return Ok(StepResult::Continue);
178 }
179 }
181
182 let (cert_pem, key_pem, pubkey_pem) = generate_certificate(&state.owner)?;
184
185 let tx = self
187 .backend
188 .broadcast_create_certificate(self.signer, &state.owner, &cert_pem, &pubkey_pem)
189 .await?;
190
191 if !tx.is_success() {
192 state.fail(format!("certificate tx failed: {}", tx.raw_log), true);
193 return Ok(StepResult::Failed(tx.raw_log));
194 }
195
196 state.record_tx(&tx.hash);
197
198 self.backend.save_cert_key(&state.owner, &key_pem).await?;
200
201 state.cert_pem = Some(cert_pem);
202 state.key_pem = Some(key_pem);
203 state.transition(Step::CreateDeployment);
204 Ok(StepResult::Continue)
205 }
206
207 async fn step_create_deployment(
208 &self,
209 state: &mut DeploymentState,
210 ) -> Result<StepResult, DeployError> {
211 let sdl = state.sdl_content.as_ref().ok_or_else(|| {
212 DeployError::InvalidState("SDL content missing at CreateDeployment".into())
213 })?;
214
215 let (tx, dseq) = self
216 .backend
217 .broadcast_create_deployment(self.signer, &state.owner, sdl, state.deposit_uakt)
218 .await?;
219
220 if !tx.is_success() {
221 state.fail(format!("create deployment tx failed: {}", tx.raw_log), true);
222 return Ok(StepResult::Failed(tx.raw_log));
223 }
224
225 state.record_tx(&tx.hash);
226 state.dseq = Some(dseq);
227 state.transition(Step::WaitForBids { waited_blocks: 0 });
228 Ok(StepResult::Continue)
229 }
230
231 async fn step_wait_for_bids(
232 &self,
233 state: &mut DeploymentState,
234 waited_blocks: u32,
235 ) -> Result<StepResult, DeployError> {
236 let dseq = state
237 .dseq
238 .ok_or_else(|| DeployError::InvalidState("dseq missing at WaitForBids".into()))?;
239
240 let bids = self.backend.query_bids(&state.owner, dseq).await?;
242
243 if !bids.is_empty() {
244 state.bids = bids;
245 state.transition(Step::SelectProvider);
246 return Ok(StepResult::Continue);
247 }
248
249 if waited_blocks >= self.config.max_bid_wait_attempts {
251 state.fail(
252 format!(
253 "no bids after {} attempts",
254 self.config.max_bid_wait_attempts
255 ),
256 true,
257 );
258 return Ok(StepResult::Failed("no bids received".into()));
259 }
260
261 tokio::time::sleep(std::time::Duration::from_secs(self.config.bid_wait_seconds)).await;
263 state.transition(Step::WaitForBids {
264 waited_blocks: waited_blocks + 1,
265 });
266 Ok(StepResult::Continue)
267 }
268
269 async fn step_select_provider(
270 &self,
271 state: &mut DeploymentState,
272 ) -> Result<StepResult, DeployError> {
273 if state.bids.is_empty() {
274 state.fail("no bids available", false);
275 return Ok(StepResult::Failed("no bids".into()));
276 }
277
278 if state.selected_provider.is_some() {
280 state.transition(Step::CreateLease);
281 return Ok(StepResult::Continue);
282 }
283
284 if self.config.auto_select_cheapest_bid {
286 let selected = self.auto_select_provider(&state.bids);
288 state.selected_provider = Some(selected.provider.clone());
289 state.transition(Step::CreateLease);
290 return Ok(StepResult::Continue);
291 }
292
293 Ok(StepResult::NeedsInput(InputRequired::SelectProvider {
295 bids: state.bids.clone(),
296 }))
297 }
298
299 async fn step_create_lease(
300 &self,
301 state: &mut DeploymentState,
302 ) -> Result<StepResult, DeployError> {
303 let dseq = state
304 .dseq
305 .ok_or_else(|| DeployError::InvalidState("dseq missing at CreateLease".into()))?;
306
307 let provider = state
308 .selected_provider
309 .as_ref()
310 .ok_or_else(|| DeployError::InvalidState("provider not selected".into()))?;
311
312 let bid = state
314 .bids
315 .iter()
316 .find(|b| &b.provider == provider)
317 .ok_or_else(|| {
318 DeployError::InvalidState(format!("no bid from provider {}", provider))
319 })?;
320
321 let bid_id = BidId::from_bid(&state.owner, dseq, state.gseq, state.oseq, bid);
322
323 let tx = self
324 .backend
325 .broadcast_create_lease(self.signer, &bid_id)
326 .await?;
327
328 if !tx.is_success() {
329 state.fail(format!("create lease tx failed: {}", tx.raw_log), true);
330 return Ok(StepResult::Failed(tx.raw_log));
331 }
332
333 state.record_tx(&tx.hash);
334 state.lease_id = Some(bid_id.into());
335 state.transition(Step::SendManifest);
336 Ok(StepResult::Continue)
337 }
338
339 async fn step_send_manifest(
340 &self,
341 state: &mut DeploymentState,
342 ) -> Result<StepResult, DeployError> {
343 let lease = state
344 .lease_id
345 .as_ref()
346 .ok_or_else(|| DeployError::InvalidState("lease_id missing at SendManifest".into()))?;
347
348 let cert = state
349 .cert_pem
350 .as_ref()
351 .ok_or_else(|| DeployError::InvalidState("cert_pem missing at SendManifest".into()))?;
352
353 let key = state
354 .key_pem
355 .as_ref()
356 .ok_or_else(|| DeployError::InvalidState("key_pem missing at SendManifest".into()))?;
357
358 let sdl = state.sdl_content.as_ref().ok_or_else(|| {
359 DeployError::InvalidState("sdl_content missing at SendManifest".into())
360 })?;
361
362 #[cfg(feature = "sdl-templates")]
364 let processed_sdl = if state.is_template {
365 let template = crate::sdl::template::SdlTemplate::new(sdl)?;
366 let empty_vars = std::collections::HashMap::new();
367 let empty_defaults = std::collections::HashMap::new();
368 let variables = state.template_variables.as_ref().unwrap_or(&empty_vars);
369 let defaults = state.template_defaults.as_ref().unwrap_or(&empty_defaults);
370 template.process(variables, defaults)?
371 } else {
372 sdl.clone()
373 };
374
375 #[cfg(not(feature = "sdl-templates"))]
376 let processed_sdl = sdl.clone();
377
378 let provider_info = self
380 .backend
381 .query_provider_info(&lease.provider)
382 .await?
383 .ok_or_else(|| DeployError::Provider("provider not found".into()))?;
384
385 let manifest = build_manifest(&state.owner, &processed_sdl, lease.dseq)?;
387
388 self.backend
389 .send_manifest(&provider_info.host_uri, lease, &manifest, cert, key)
390 .await?;
391
392 state.transition(Step::WaitForEndpoints { attempts: 0 });
393 Ok(StepResult::Continue)
394 }
395
396 async fn step_wait_for_endpoints(
397 &self,
398 state: &mut DeploymentState,
399 attempts: u32,
400 ) -> Result<StepResult, DeployError> {
401 let lease = state.lease_id.as_ref().ok_or_else(|| {
402 DeployError::InvalidState("lease_id missing at WaitForEndpoints".into())
403 })?;
404
405 let cert = state.cert_pem.as_ref().ok_or_else(|| {
406 DeployError::InvalidState("cert_pem missing at WaitForEndpoints".into())
407 })?;
408
409 let key = state.key_pem.as_ref().ok_or_else(|| {
410 DeployError::InvalidState("key_pem missing at WaitForEndpoints".into())
411 })?;
412
413 let provider_info = self
414 .backend
415 .query_provider_info(&lease.provider)
416 .await?
417 .ok_or_else(|| DeployError::Provider("provider not found".into()))?;
418
419 let status = self
420 .backend
421 .query_provider_status(&provider_info.host_uri, lease, cert, key)
422 .await?;
423
424 if status.ready && !status.endpoints.is_empty() {
425 state.endpoints = status.endpoints;
426 state.transition(Step::Complete);
427 return Ok(StepResult::Complete);
428 }
429
430 if attempts >= self.config.max_endpoint_wait_attempts {
431 state.fail(
432 format!("endpoints not ready after {} attempts", attempts),
433 true,
434 );
435 return Ok(StepResult::Failed("endpoints not ready".into()));
436 }
437
438 tokio::time::sleep(std::time::Duration::from_secs(5)).await;
440 state.transition(Step::WaitForEndpoints {
441 attempts: attempts + 1,
442 });
443 Ok(StepResult::Continue)
444 }
445
446 fn auto_select_provider<'b>(&self, bids: &'b [Bid]) -> &'b Bid {
451 for trusted in &self.config.trusted_providers {
453 if let Some(bid) = bids.iter().find(|b| &b.provider == trusted) {
454 return bid;
455 }
456 }
457 bids.iter()
459 .min_by_key(|b| b.price_uakt)
460 .expect("bids should not be empty")
461 }
462
463 pub fn select_provider(state: &mut DeploymentState, provider: &str) -> Result<(), DeployError> {
465 if !state.bids.iter().any(|b| b.provider == provider) {
466 return Err(DeployError::InvalidState(format!(
467 "provider {} not in available bids",
468 provider
469 )));
470 }
471 state.selected_provider = Some(provider.to_string());
472 Ok(())
473 }
474
475 pub fn provide_sdl(state: &mut DeploymentState, sdl: &str) {
477 state.sdl_content = Some(sdl.to_string());
478 }
479}
480
481fn generate_certificate(owner: &str) -> Result<(Vec<u8>, Vec<u8>, Vec<u8>), DeployError> {
488 let cert = crate::auth::certificate::generate_certificate(owner)?;
489 Ok((cert.cert_pem, cert.privkey_pem, cert.pubkey_pem))
490}
491
492fn build_manifest(owner: &str, sdl: &str, dseq: u64) -> Result<Vec<u8>, DeployError> {
498 if sdl.is_empty() {
499 return Err(DeployError::Manifest("empty SDL".into()));
500 }
501
502 let builder = crate::manifest::manifest::ManifestBuilder::new(owner, dseq);
504 let manifest_groups = builder.build_from_sdl(sdl)?;
505
506 let canonical_json = crate::manifest::canonical::to_canonical_json(&manifest_groups)?;
508
509 Ok(canonical_json.into_bytes())
510}
511
512#[cfg(test)]
513mod tests {
514 use super::*;
515 use crate::types::*;
516 use std::sync::{Arc, Mutex};
517
518 const SIMPLE_SDL: &str = r#"
520version: "2.0"
521services:
522 web:
523 image: nginx
524 expose:
525 - port: 80
526 as: 80
527 to:
528 - global: true
529profiles:
530 compute:
531 web:
532 resources:
533 cpu:
534 units: 1
535 memory:
536 size: 512Mi
537 storage:
538 size: 1Gi
539 placement:
540 dc:
541 pricing:
542 web:
543 denom: uakt
544 amount: 1000
545deployment:
546 web:
547 dc:
548 profile: web
549 count: 1
550"#;
551
552 #[derive(Debug, Clone)]
554 struct MockSigner;
555
556 struct MockBackend {
558 balance: Arc<Mutex<u128>>,
559 certificate: Arc<Mutex<Option<CertificateInfo>>>,
560 cert_key: Arc<Mutex<Option<Vec<u8>>>>,
561 bids: Arc<Mutex<Vec<Bid>>>,
562 provider_status: Arc<Mutex<Option<ProviderLeaseStatus>>>,
563 call_counts: Arc<Mutex<CallCounts>>,
564 fail_cert_tx: Arc<Mutex<bool>>,
565 fail_deployment_tx: Arc<Mutex<bool>>,
566 fail_lease_tx: Arc<Mutex<bool>>,
567 }
568
569 #[derive(Debug, Default, Clone)]
570 struct CallCounts {
571 query_balance: usize,
572 query_bids: usize,
573 broadcast_create_deployment: usize,
574 broadcast_create_lease: usize,
575 send_manifest: usize,
576 query_provider_status: usize,
577 }
578
579 impl MockBackend {
580 fn new() -> Self {
581 Self {
582 balance: Arc::new(Mutex::new(10_000_000)), certificate: Arc::new(Mutex::new(None)),
584 cert_key: Arc::new(Mutex::new(None)),
585 bids: Arc::new(Mutex::new(Vec::new())),
586 provider_status: Arc::new(Mutex::new(None)),
587 call_counts: Arc::new(Mutex::new(CallCounts::default())),
588 fail_cert_tx: Arc::new(Mutex::new(false)),
589 fail_deployment_tx: Arc::new(Mutex::new(false)),
590 fail_lease_tx: Arc::new(Mutex::new(false)),
591 }
592 }
593
594 fn set_balance(&self, balance: u128) {
595 *self.balance.lock().unwrap() = balance;
596 }
597
598 fn set_bids(&self, bids: Vec<Bid>) {
599 *self.bids.lock().unwrap() = bids;
600 }
601
602 fn set_provider_status(&self, status: ProviderLeaseStatus) {
603 *self.provider_status.lock().unwrap() = Some(status);
604 }
605
606 fn set_certificate(&self, cert: CertificateInfo) {
607 *self.certificate.lock().unwrap() = Some(cert);
608 }
609
610 fn set_cert_key(&self, key: Vec<u8>) {
611 *self.cert_key.lock().unwrap() = Some(key);
612 }
613
614 fn set_fail_cert_tx(&self, fail: bool) {
615 *self.fail_cert_tx.lock().unwrap() = fail;
616 }
617
618 fn set_fail_deployment_tx(&self, fail: bool) {
619 *self.fail_deployment_tx.lock().unwrap() = fail;
620 }
621
622 fn set_fail_lease_tx(&self, fail: bool) {
623 *self.fail_lease_tx.lock().unwrap() = fail;
624 }
625
626 fn get_call_counts(&self) -> CallCounts {
627 self.call_counts.lock().unwrap().clone()
628 }
629 }
630
631 impl AkashBackend for MockBackend {
632 type Signer = MockSigner;
633
634 async fn query_balance(&self, _address: &str, _denom: &str) -> Result<u128, DeployError> {
635 self.call_counts.lock().unwrap().query_balance += 1;
636 Ok(*self.balance.lock().unwrap())
637 }
638
639 async fn query_certificate(
640 &self,
641 _address: &str,
642 ) -> Result<Option<CertificateInfo>, DeployError> {
643 Ok(self.certificate.lock().unwrap().clone())
644 }
645
646 async fn query_provider_info(
647 &self,
648 _provider: &str,
649 ) -> Result<Option<ProviderInfo>, DeployError> {
650 Ok(Some(ProviderInfo {
651 address: "akash1provider".to_string(),
652 host_uri: "https://provider.akash.net".to_string(),
653 email: "test@example.com".to_string(),
654 website: "https://example.com".to_string(),
655 attributes: vec![],
656 cached_at: 0,
657 }))
658 }
659
660 async fn query_bids(&self, _owner: &str, _dseq: u64) -> Result<Vec<Bid>, DeployError> {
661 self.call_counts.lock().unwrap().query_bids += 1;
662 Ok(self.bids.lock().unwrap().clone())
663 }
664
665 async fn query_lease(
666 &self,
667 _owner: &str,
668 _dseq: u64,
669 _gseq: u32,
670 _oseq: u32,
671 _bseq: u32,
672 _provider: &str,
673 ) -> Result<LeaseInfo, DeployError> {
674 Ok(LeaseInfo {
675 state: LeaseState::Active,
676 price_uakt: 1000,
677 })
678 }
679
680 async fn query_escrow(&self, _owner: &str, _dseq: u64) -> Result<EscrowInfo, DeployError> {
681 Ok(EscrowInfo {
682 balance_uakt: 5_000_000,
683 deposited_uakt: 5_000_000,
684 })
685 }
686
687 async fn broadcast_create_certificate(
688 &self,
689 _signer: &Self::Signer,
690 _owner: &str,
691 _cert_pem: &[u8],
692 _pubkey_pem: &[u8],
693 ) -> Result<TxResult, DeployError> {
694 if *self.fail_cert_tx.lock().unwrap() {
695 Ok(TxResult {
696 hash: "CERT_TX_FAIL".to_string(),
697 code: 5,
698 raw_log: "certificate creation failed".to_string(),
699 height: 1000,
700 })
701 } else {
702 Ok(TxResult {
703 hash: "CERT_TX".to_string(),
704 code: 0,
705 raw_log: "success".to_string(),
706 height: 1000,
707 })
708 }
709 }
710
711 async fn broadcast_create_deployment(
712 &self,
713 _signer: &Self::Signer,
714 _owner: &str,
715 _sdl_content: &str,
716 _deposit_uakt: u64,
717 ) -> Result<(TxResult, u64), DeployError> {
718 self.call_counts.lock().unwrap().broadcast_create_deployment += 1;
719 if *self.fail_deployment_tx.lock().unwrap() {
720 Ok((
721 TxResult {
722 hash: "DEPLOY_TX_FAIL".to_string(),
723 code: 5,
724 raw_log: "deployment creation failed".to_string(),
725 height: 1001,
726 },
727 123456,
728 ))
729 } else {
730 Ok((
731 TxResult {
732 hash: "DEPLOY_TX".to_string(),
733 code: 0,
734 raw_log: "success".to_string(),
735 height: 1001,
736 },
737 123456,
738 ))
739 }
740 }
741
742 async fn broadcast_create_lease(
743 &self,
744 _signer: &Self::Signer,
745 _bid: &BidId,
746 ) -> Result<TxResult, DeployError> {
747 self.call_counts.lock().unwrap().broadcast_create_lease += 1;
748 if *self.fail_lease_tx.lock().unwrap() {
749 Ok(TxResult {
750 hash: "LEASE_TX_FAIL".to_string(),
751 code: 5,
752 raw_log: "lease creation failed".to_string(),
753 height: 1002,
754 })
755 } else {
756 Ok(TxResult {
757 hash: "LEASE_TX".to_string(),
758 code: 0,
759 raw_log: "success".to_string(),
760 height: 1002,
761 })
762 }
763 }
764
765 async fn broadcast_deposit(
766 &self,
767 _signer: &Self::Signer,
768 _owner: &str,
769 _dseq: u64,
770 _amount_uakt: u64,
771 ) -> Result<TxResult, DeployError> {
772 Ok(TxResult {
773 hash: "DEPOSIT_TX".to_string(),
774 code: 0,
775 raw_log: "success".to_string(),
776 height: 1003,
777 })
778 }
779
780 async fn broadcast_close_deployment(
781 &self,
782 _signer: &Self::Signer,
783 _owner: &str,
784 _dseq: u64,
785 ) -> Result<TxResult, DeployError> {
786 Ok(TxResult {
787 hash: "CLOSE_TX".to_string(),
788 code: 0,
789 raw_log: "success".to_string(),
790 height: 1004,
791 })
792 }
793
794 async fn send_manifest(
795 &self,
796 _provider_uri: &str,
797 _lease: &LeaseId,
798 _manifest: &[u8],
799 _cert_pem: &[u8],
800 _key_pem: &[u8],
801 ) -> Result<(), DeployError> {
802 self.call_counts.lock().unwrap().send_manifest += 1;
803 Ok(())
804 }
805
806 async fn query_provider_status(
807 &self,
808 _provider_uri: &str,
809 _lease: &LeaseId,
810 _cert_pem: &[u8],
811 _key_pem: &[u8],
812 ) -> Result<ProviderLeaseStatus, DeployError> {
813 self.call_counts.lock().unwrap().query_provider_status += 1;
814 self.provider_status
815 .lock()
816 .unwrap()
817 .clone()
818 .ok_or_else(|| DeployError::Provider("no status".into()))
819 }
820
821 async fn load_state(
822 &self,
823 _session_id: &str,
824 ) -> Result<Option<DeploymentState>, DeployError> {
825 Ok(None)
826 }
827
828 async fn save_state(
829 &self,
830 _session_id: &str,
831 _state: &DeploymentState,
832 ) -> Result<(), DeployError> {
833 Ok(())
834 }
835
836 async fn load_cert_key(&self, _owner: &str) -> Result<Option<Vec<u8>>, DeployError> {
837 Ok(None)
838 }
839
840 async fn save_cert_key(&self, _owner: &str, _key: &[u8]) -> Result<(), DeployError> {
841 Ok(())
842 }
843
844 async fn delete_cert_key(&self, _owner: &str) -> Result<(), DeployError> {
845 Ok(())
846 }
847
848 async fn load_cached_provider(
849 &self,
850 _provider: &str,
851 ) -> Result<Option<ProviderInfo>, DeployError> {
852 Ok(None)
853 }
854
855 async fn cache_provider(&self, _info: &ProviderInfo) -> Result<(), DeployError> {
856 Ok(())
857 }
858 }
859
860 #[test]
861 fn test_default_config() {
862 let config = WorkflowConfig::default();
863 assert_eq!(config.min_balance_uakt, 5_000_000);
864 assert!(!config.auto_select_cheapest_bid);
865 }
866
867 #[test]
868 fn test_step_result_variants() {
869 let _ = StepResult::Continue;
870 let _ = StepResult::Complete;
871 let _ = StepResult::Failed("oops".into());
872 let _ = StepResult::NeedsInput(InputRequired::ProvideSdl);
873 }
874
875 #[tokio::test]
876 async fn test_workflow_check_balance_sufficient() {
877 let backend = MockBackend::new();
878 let signer = MockSigner;
879 let config = WorkflowConfig::default();
880 let workflow = DeploymentWorkflow::new(&backend, &signer, config);
881
882 let mut state = DeploymentState::new("test", "akash1owner");
883 state.step = Step::CheckBalance;
884
885 let result = workflow.advance(&mut state).await.unwrap();
886 assert!(matches!(result, StepResult::Continue));
887 assert_eq!(backend.get_call_counts().query_balance, 1);
888 }
889
890 #[tokio::test]
891 async fn test_workflow_check_balance_insufficient() {
892 let backend = MockBackend::new();
893 backend.set_balance(1_000_000);
894
895 let signer = MockSigner;
896 let config = WorkflowConfig::default();
897 let workflow = DeploymentWorkflow::new(&backend, &signer, config);
898
899 let mut state = DeploymentState::new("test", "akash1owner");
900 state.step = Step::CheckBalance;
901
902 let result = workflow.advance(&mut state).await.unwrap();
903 assert!(matches!(result, StepResult::Failed(_)));
904 }
905
906 #[tokio::test]
907 async fn test_workflow_wait_for_bids() {
908 let backend = MockBackend::new();
909 backend.set_bids(vec![Bid {
910 provider: "akash1provider".to_string(),
911 price_uakt: 1000,
912 resources: Resources::default(),
913 }]);
914
915 let signer = MockSigner;
916 let config = WorkflowConfig::default();
917 let workflow = DeploymentWorkflow::new(&backend, &signer, config);
918
919 let mut state = DeploymentState::new("test", "akash1owner");
920 state.step = Step::WaitForBids { waited_blocks: 0 };
921 state.dseq = Some(123456);
922
923 let result = workflow.advance(&mut state).await.unwrap();
924 assert!(matches!(result, StepResult::Continue));
925 assert_eq!(state.bids.len(), 1);
926 }
927
928 #[tokio::test]
929 async fn test_workflow_auto_select_cheapest() {
930 let backend = MockBackend::new();
931
932 let signer = MockSigner;
933 let mut config = WorkflowConfig::default();
934 config.auto_select_cheapest_bid = true;
935 let workflow = DeploymentWorkflow::new(&backend, &signer, config);
936
937 let mut state = DeploymentState::new("test", "akash1owner");
938 state.step = Step::SelectProvider;
939 state.bids = vec![
940 Bid {
941 provider: "akash1expensive".to_string(),
942 price_uakt: 5000,
943 resources: Resources::default(),
944 },
945 Bid {
946 provider: "akash1cheap".to_string(),
947 price_uakt: 1000,
948 resources: Resources::default(),
949 },
950 ];
951
952 let result = workflow.advance(&mut state).await.unwrap();
953 assert!(matches!(result, StepResult::Continue));
954 assert_eq!(state.selected_provider, Some("akash1cheap".to_string()));
955 }
956
957 #[tokio::test]
958 async fn test_workflow_endpoints_ready() {
959 let backend = MockBackend::new();
960 backend.set_provider_status(ProviderLeaseStatus {
961 ready: true,
962 endpoints: vec![ServiceEndpoint {
963 service: "web".to_string(),
964 uri: "https://web.example.com".to_string(),
965 port: 80,
966 }],
967 });
968
969 let signer = MockSigner;
970 let config = WorkflowConfig::default();
971 let workflow = DeploymentWorkflow::new(&backend, &signer, config);
972
973 let mut state = DeploymentState::new("test", "akash1owner");
974 state.step = Step::WaitForEndpoints { attempts: 0 };
975 state.dseq = Some(123456);
976 state.selected_provider = Some("akash1provider".to_string());
977 state.lease_id = Some(LeaseId {
978 owner: "akash1owner".to_string(),
979 dseq: 123456,
980 gseq: 1,
981 oseq: 1,
982 provider: "akash1provider".to_string(),
983 });
984 state.cert_pem = Some(vec![1, 2, 3]); state.key_pem = Some(vec![4, 5, 6]); let result = workflow.advance(&mut state).await.unwrap();
988 assert!(matches!(result, StepResult::Complete));
989 assert_eq!(state.endpoints.len(), 1);
990 }
991
992 #[tokio::test]
993 async fn test_select_provider_invalid() {
994 let mut state = DeploymentState::new("test", "akash1owner");
995 state.bids = vec![Bid {
996 provider: "akash1provider1".to_string(),
997 price_uakt: 1000,
998 resources: Resources::default(),
999 }];
1000
1001 let result =
1002 DeploymentWorkflow::<MockBackend>::select_provider(&mut state, "akash1nonexistent");
1003 assert!(result.is_err());
1004 }
1005
1006 #[tokio::test]
1007 async fn test_select_provider_valid() {
1008 let mut state = DeploymentState::new("test", "akash1owner");
1009 state.bids = vec![Bid {
1010 provider: "akash1provider1".to_string(),
1011 price_uakt: 1000,
1012 resources: Resources::default(),
1013 }];
1014
1015 DeploymentWorkflow::<MockBackend>::select_provider(&mut state, "akash1provider1").unwrap();
1016 assert_eq!(state.selected_provider, Some("akash1provider1".to_string()));
1017 }
1018
1019 #[tokio::test]
1020 async fn test_provide_sdl() {
1021 let mut state = DeploymentState::new("test", "akash1owner");
1022 DeploymentWorkflow::<MockBackend>::provide_sdl(&mut state, "version: 2.0");
1023 assert_eq!(state.sdl_content, Some("version: 2.0".to_string()));
1024 }
1025
1026 #[tokio::test]
1027 async fn test_step_init_missing_sdl() {
1028 let backend = MockBackend::new();
1029 let signer = MockSigner;
1030 let config = WorkflowConfig::default();
1031 let workflow = DeploymentWorkflow::new(&backend, &signer, config);
1032
1033 let mut state = DeploymentState::new("test", "akash1owner");
1034 state.step = Step::Init;
1035
1036 let result = workflow.advance(&mut state).await.unwrap();
1037 assert!(matches!(
1038 result,
1039 StepResult::NeedsInput(InputRequired::ProvideSdl)
1040 ));
1041 }
1042
1043 #[tokio::test]
1044 async fn test_run_to_completion_needs_input() {
1045 let backend = MockBackend::new();
1046 let signer = MockSigner;
1047 let config = WorkflowConfig::default();
1048 let workflow = DeploymentWorkflow::new(&backend, &signer, config);
1049
1050 let mut state = DeploymentState::new("test", "akash1owner");
1051 state.step = Step::Init;
1052
1053 let result = workflow.run_to_completion(&mut state).await.unwrap();
1054 assert!(matches!(result, StepResult::NeedsInput(_)));
1055 }
1056
1057 #[tokio::test]
1058 async fn test_step_init_with_sdl() {
1059 let backend = MockBackend::new();
1060 let signer = MockSigner;
1061 let config = WorkflowConfig::default();
1062 let workflow = DeploymentWorkflow::new(&backend, &signer, config);
1063
1064 let mut state = DeploymentState::new("test", "akash1owner");
1065 state.step = Step::Init;
1066 state.sdl_content = Some("version: 2.0".to_string());
1067
1068 let result = workflow.advance(&mut state).await.unwrap();
1069 assert!(matches!(result, StepResult::Continue));
1070 assert!(matches!(state.step, Step::CheckBalance));
1071 }
1072
1073 #[tokio::test]
1074 async fn test_step_complete() {
1075 let backend = MockBackend::new();
1076 let signer = MockSigner;
1077 let config = WorkflowConfig::default();
1078 let workflow = DeploymentWorkflow::new(&backend, &signer, config);
1079
1080 let mut state = DeploymentState::new("test", "akash1owner");
1081 state.step = Step::Complete;
1082
1083 let result = workflow.advance(&mut state).await.unwrap();
1084 assert!(matches!(result, StepResult::Complete));
1085 }
1086
1087 #[tokio::test]
1088 async fn test_step_failed() {
1089 let backend = MockBackend::new();
1090 let signer = MockSigner;
1091 let config = WorkflowConfig::default();
1092 let workflow = DeploymentWorkflow::new(&backend, &signer, config);
1093
1094 let mut state = DeploymentState::new("test", "akash1owner");
1095 state.step = Step::Failed {
1096 reason: "test error".to_string(),
1097 recoverable: false,
1098 };
1099
1100 let result = workflow.advance(&mut state).await.unwrap();
1101 assert!(matches!(result, StepResult::Failed(_)));
1102 }
1103
1104 #[tokio::test]
1105 async fn test_step_create_deployment_missing_dseq() {
1106 let backend = MockBackend::new();
1107 let signer = MockSigner;
1108 let config = WorkflowConfig::default();
1109 let workflow = DeploymentWorkflow::new(&backend, &signer, config);
1110
1111 let mut state = DeploymentState::new("test", "akash1owner");
1112 state.step = Step::CreateDeployment;
1113 state.sdl_content = Some(SIMPLE_SDL.to_string());
1114
1115 let result = workflow.advance(&mut state).await.unwrap();
1116 assert!(matches!(result, StepResult::Continue));
1117 assert!(matches!(state.step, Step::WaitForBids { .. }));
1118 assert!(state.dseq.is_some());
1119 }
1120
1121 #[tokio::test]
1122 async fn test_step_create_lease_missing_provider() {
1123 let backend = MockBackend::new();
1124 let signer = MockSigner;
1125 let config = WorkflowConfig::default();
1126 let workflow = DeploymentWorkflow::new(&backend, &signer, config);
1127
1128 let mut state = DeploymentState::new("test", "akash1owner");
1129 state.step = Step::CreateLease;
1130 state.dseq = Some(123456);
1131 let result = workflow.advance(&mut state).await;
1134 assert!(result.is_err() || matches!(result, Ok(StepResult::Failed(_))));
1135 }
1136
1137 #[tokio::test]
1138 async fn test_step_send_manifest_missing_sdl() {
1139 let backend = MockBackend::new();
1140 let signer = MockSigner;
1141 let config = WorkflowConfig::default();
1142 let workflow = DeploymentWorkflow::new(&backend, &signer, config);
1143
1144 let mut state = DeploymentState::new("test", "akash1owner");
1145 state.step = Step::SendManifest;
1146 state.dseq = Some(123456);
1147 state.selected_provider = Some("akash1provider".to_string());
1148 state.cert_pem = Some(vec![1, 2, 3]);
1149 state.key_pem = Some(vec![4, 5, 6]);
1150 let result = workflow.advance(&mut state).await;
1153 assert!(result.is_err() || matches!(result, Ok(StepResult::Failed(_))));
1154 }
1155
1156 #[tokio::test]
1157 async fn test_step_wait_for_endpoints_missing_lease_id() {
1158 let backend = MockBackend::new();
1159 let signer = MockSigner;
1160 let config = WorkflowConfig::default();
1161 let workflow = DeploymentWorkflow::new(&backend, &signer, config);
1162
1163 let mut state = DeploymentState::new("test", "akash1owner");
1164 state.step = Step::WaitForEndpoints { attempts: 0 };
1165 state.dseq = Some(123456);
1166 state.selected_provider = Some("akash1provider".to_string());
1167 state.cert_pem = Some(vec![1, 2, 3]);
1168 state.key_pem = Some(vec![4, 5, 6]);
1169 let result = workflow.advance(&mut state).await;
1172 assert!(result.is_err());
1173 }
1174
1175 #[tokio::test]
1176 async fn test_step_wait_for_endpoints_missing_cert() {
1177 let backend = MockBackend::new();
1178 let signer = MockSigner;
1179 let config = WorkflowConfig::default();
1180 let workflow = DeploymentWorkflow::new(&backend, &signer, config);
1181
1182 let mut state = DeploymentState::new("test", "akash1owner");
1183 state.step = Step::WaitForEndpoints { attempts: 0 };
1184 state.dseq = Some(123456);
1185 state.selected_provider = Some("akash1provider".to_string());
1186 state.lease_id = Some(LeaseId {
1187 owner: "akash1owner".to_string(),
1188 dseq: 123456,
1189 gseq: 1,
1190 oseq: 1,
1191 provider: "akash1provider".to_string(),
1192 });
1193 let result = workflow.advance(&mut state).await;
1196 assert!(result.is_err());
1197 }
1198
1199 #[test]
1200 fn test_build_manifest_empty() {
1201 let result = build_manifest("akash1owner", "", 123);
1202 assert!(result.is_err());
1203 assert!(result.unwrap_err().to_string().contains("empty"));
1204 }
1205
1206 #[test]
1207 fn test_build_manifest_valid_sdl() {
1208 let result = build_manifest("akash1owner", SIMPLE_SDL, 123);
1209 assert!(result.is_ok());
1210 let manifest_bytes = result.unwrap();
1211 assert!(!manifest_bytes.is_empty());
1212 }
1213
1214 #[test]
1215 fn test_generate_cert() {
1216 let result = generate_certificate("akash1owner");
1217 assert!(result.is_ok());
1218 let (cert_pem, key_pem, pubkey_pem) = result.unwrap();
1219 assert!(!cert_pem.is_empty());
1220 assert!(!key_pem.is_empty());
1221 assert!(!pubkey_pem.is_empty());
1222 }
1223
1224 #[tokio::test]
1225 async fn test_step_select_provider_with_trusted() {
1226 let backend = MockBackend::new();
1227 let signer = MockSigner;
1228 let mut config = WorkflowConfig::default();
1229 config.trusted_providers = vec!["akash1trusted".to_string()];
1230 let workflow = DeploymentWorkflow::new(&backend, &signer, config);
1231
1232 let mut state = DeploymentState::new("test", "akash1owner");
1233 state.step = Step::SelectProvider;
1234 state.bids = vec![
1235 Bid {
1236 provider: "akash1trusted".to_string(),
1237 price_uakt: 5000,
1238 resources: Resources::default(),
1239 },
1240 Bid {
1241 provider: "akash1cheap".to_string(),
1242 price_uakt: 1000,
1243 resources: Resources::default(),
1244 },
1245 ];
1246
1247 let result = workflow.advance(&mut state).await.unwrap();
1248 assert!(matches!(result, StepResult::NeedsInput(_)));
1250 }
1251
1252 #[tokio::test]
1253 async fn test_step_create_deployment_with_valid_sdl() {
1254 let backend = MockBackend::new();
1255 let signer = MockSigner;
1256 let config = WorkflowConfig::default();
1257 let workflow = DeploymentWorkflow::new(&backend, &signer, config);
1258
1259 let mut state = DeploymentState::new("test", "akash1owner");
1260 state.step = Step::CreateDeployment;
1261 state.sdl_content = Some(SIMPLE_SDL.to_string());
1262
1263 let result = workflow.advance(&mut state).await.unwrap();
1264 assert!(matches!(result, StepResult::Continue));
1265 assert!(state.dseq.is_some());
1266 assert!(matches!(state.step, Step::WaitForBids { .. }));
1267 }
1268
1269 #[tokio::test]
1270 async fn test_full_workflow_to_bids() {
1271 let backend = MockBackend::new();
1272 backend.set_bids(vec![Bid {
1273 provider: "akash1provider".to_string(),
1274 price_uakt: 1000,
1275 resources: Resources::default(),
1276 }]);
1277
1278 let signer = MockSigner;
1279 let config = WorkflowConfig::default();
1280 let workflow = DeploymentWorkflow::new(&backend, &signer, config);
1281
1282 let mut state = DeploymentState::new("test", "akash1owner");
1283 state.sdl_content = Some(SIMPLE_SDL.to_string());
1284
1285 let mut steps = 0;
1287 loop {
1288 let result = workflow.advance(&mut state).await.unwrap();
1289 steps += 1;
1290
1291 match result {
1292 StepResult::Continue => {
1293 if steps > 10 {
1294 panic!("Too many steps");
1295 }
1296 continue;
1297 }
1298 StepResult::NeedsInput(_) => break,
1299 StepResult::Complete => break,
1300 StepResult::Failed(reason) => panic!("Failed: {}", reason),
1301 }
1302 }
1303
1304 assert!(!state.bids.is_empty());
1306 assert!(matches!(state.step, Step::SelectProvider));
1307 }
1308
1309 #[tokio::test]
1310 async fn test_workflow_wait_for_bids_timeout() {
1311 let backend = MockBackend::new();
1312 backend.set_bids(vec![]); let signer = MockSigner;
1314 let mut config = WorkflowConfig::default();
1315 config.max_bid_wait_attempts = 2; config.bid_wait_seconds = 0; let workflow = DeploymentWorkflow::new(&backend, &signer, config);
1318
1319 let mut state = DeploymentState::new("test", "akash1owner");
1320 state.dseq = Some(123456);
1321 state.step = Step::WaitForBids { waited_blocks: 0 };
1322
1323 let result = workflow.advance(&mut state).await.unwrap();
1325 assert!(matches!(result, StepResult::Continue));
1326 assert!(matches!(state.step, Step::WaitForBids { waited_blocks: 1 }));
1327
1328 let result = workflow.advance(&mut state).await.unwrap();
1330 assert!(matches!(result, StepResult::Continue));
1331 assert!(matches!(state.step, Step::WaitForBids { waited_blocks: 2 }));
1332
1333 let result = workflow.advance(&mut state).await.unwrap();
1335 assert!(matches!(result, StepResult::Failed(_)));
1336 }
1337
1338 #[tokio::test]
1339 async fn test_workflow_select_provider_no_bids() {
1340 let backend = MockBackend::new();
1341 let signer = MockSigner;
1342 let config = WorkflowConfig::default();
1343 let workflow = DeploymentWorkflow::new(&backend, &signer, config);
1344
1345 let mut state = DeploymentState::new("test", "akash1owner");
1346 state.step = Step::SelectProvider;
1347 state.bids = vec![]; let result = workflow.advance(&mut state).await.unwrap();
1350 assert!(matches!(result, StepResult::Failed(_)));
1351 }
1352
1353 #[tokio::test]
1354 async fn test_workflow_select_provider_already_selected() {
1355 let backend = MockBackend::new();
1356 let signer = MockSigner;
1357 let config = WorkflowConfig::default();
1358 let workflow = DeploymentWorkflow::new(&backend, &signer, config);
1359
1360 let mut state = DeploymentState::new("test", "akash1owner");
1361 state.step = Step::SelectProvider;
1362 state.bids = vec![Bid {
1363 provider: "akash1provider".to_string(),
1364 price_uakt: 1000,
1365 resources: Resources::default(),
1366 }];
1367 state.selected_provider = Some("akash1provider".to_string());
1368
1369 let result = workflow.advance(&mut state).await.unwrap();
1370 assert!(matches!(result, StepResult::Continue));
1371 assert!(matches!(state.step, Step::CreateLease));
1372 }
1373
1374 #[tokio::test]
1375 async fn test_workflow_select_provider_auto_select() {
1376 let backend = MockBackend::new();
1377 let signer = MockSigner;
1378 let mut config = WorkflowConfig::default();
1379 config.auto_select_cheapest_bid = true;
1380 let workflow = DeploymentWorkflow::new(&backend, &signer, config);
1381
1382 let mut state = DeploymentState::new("test", "akash1owner");
1383 state.step = Step::SelectProvider;
1384 state.bids = vec![
1385 Bid {
1386 provider: "akash1expensive".to_string(),
1387 price_uakt: 5000,
1388 resources: Resources::default(),
1389 },
1390 Bid {
1391 provider: "akash1cheap".to_string(),
1392 price_uakt: 1000,
1393 resources: Resources::default(),
1394 },
1395 ];
1396
1397 let result = workflow.advance(&mut state).await.unwrap();
1398 assert!(matches!(result, StepResult::Continue));
1399 assert_eq!(state.selected_provider, Some("akash1cheap".to_string()));
1400 }
1401
1402 #[tokio::test]
1403 async fn test_workflow_auto_select_trusted_provider() {
1404 let backend = MockBackend::new();
1405 let signer = MockSigner;
1406 let mut config = WorkflowConfig::default();
1407 config.auto_select_cheapest_bid = true;
1408 config.trusted_providers = vec!["akash1trusted".to_string()];
1409 let workflow = DeploymentWorkflow::new(&backend, &signer, config);
1410
1411 let mut state = DeploymentState::new("test", "akash1owner");
1412 state.step = Step::SelectProvider;
1413 state.bids = vec![
1414 Bid {
1415 provider: "akash1trusted".to_string(),
1416 price_uakt: 5000, resources: Resources::default(),
1418 },
1419 Bid {
1420 provider: "akash1cheap".to_string(),
1421 price_uakt: 1000, resources: Resources::default(),
1423 },
1424 ];
1425
1426 let result = workflow.advance(&mut state).await.unwrap();
1427 assert!(matches!(result, StepResult::Continue));
1428 assert_eq!(state.selected_provider, Some("akash1trusted".to_string()));
1430 }
1431
1432 #[tokio::test]
1433 async fn test_workflow_create_lease_missing_dseq() {
1434 let backend = MockBackend::new();
1435 let signer = MockSigner;
1436 let config = WorkflowConfig::default();
1437 let workflow = DeploymentWorkflow::new(&backend, &signer, config);
1438
1439 let mut state = DeploymentState::new("test", "akash1owner");
1440 state.step = Step::CreateLease;
1441 state.selected_provider = Some("akash1provider".to_string());
1442 let result = workflow.advance(&mut state).await;
1445 assert!(result.is_err());
1446 }
1447
1448 #[tokio::test]
1449 async fn test_workflow_create_lease_no_provider_selected() {
1450 let backend = MockBackend::new();
1451 let signer = MockSigner;
1452 let config = WorkflowConfig::default();
1453 let workflow = DeploymentWorkflow::new(&backend, &signer, config);
1454
1455 let mut state = DeploymentState::new("test", "akash1owner");
1456 state.step = Step::CreateLease;
1457 state.dseq = Some(123456);
1458 let result = workflow.advance(&mut state).await;
1461 assert!(result.is_err());
1462 }
1463
1464 #[tokio::test]
1465 async fn test_workflow_create_lease_bid_not_found() {
1466 let backend = MockBackend::new();
1467 let signer = MockSigner;
1468 let config = WorkflowConfig::default();
1469 let workflow = DeploymentWorkflow::new(&backend, &signer, config);
1470
1471 let mut state = DeploymentState::new("test", "akash1owner");
1472 state.step = Step::CreateLease;
1473 state.dseq = Some(123456);
1474 state.selected_provider = Some("akash1nonexistent".to_string());
1475 state.bids = vec![Bid {
1476 provider: "akash1other".to_string(),
1477 price_uakt: 1000,
1478 resources: Resources::default(),
1479 }];
1480
1481 let result = workflow.advance(&mut state).await;
1482 assert!(result.is_err());
1483 }
1484
1485 #[tokio::test]
1486 async fn test_workflow_send_manifest_missing_lease_id() {
1487 let backend = MockBackend::new();
1488 let signer = MockSigner;
1489 let config = WorkflowConfig::default();
1490 let workflow = DeploymentWorkflow::new(&backend, &signer, config);
1491
1492 let mut state = DeploymentState::new("test", "akash1owner");
1493 state.step = Step::SendManifest;
1494 state.cert_pem = Some(vec![1, 2, 3]);
1495 state.key_pem = Some(vec![4, 5, 6]);
1496 state.sdl_content = Some(SIMPLE_SDL.to_string());
1497 let result = workflow.advance(&mut state).await;
1500 assert!(result.is_err());
1501 }
1502
1503 #[tokio::test]
1504 async fn test_workflow_send_manifest_missing_cert() {
1505 let backend = MockBackend::new();
1506 let signer = MockSigner;
1507 let config = WorkflowConfig::default();
1508 let workflow = DeploymentWorkflow::new(&backend, &signer, config);
1509
1510 let mut state = DeploymentState::new("test", "akash1owner");
1511 state.step = Step::SendManifest;
1512 state.lease_id = Some(LeaseId {
1513 owner: "akash1owner".to_string(),
1514 dseq: 123456,
1515 gseq: 1,
1516 oseq: 1,
1517 provider: "akash1provider".to_string(),
1518 });
1519 state.sdl_content = Some(SIMPLE_SDL.to_string());
1520 let result = workflow.advance(&mut state).await;
1523 assert!(result.is_err());
1524 }
1525
1526 #[tokio::test]
1527 async fn test_workflow_send_manifest_missing_key() {
1528 let backend = MockBackend::new();
1529 let signer = MockSigner;
1530 let config = WorkflowConfig::default();
1531 let workflow = DeploymentWorkflow::new(&backend, &signer, config);
1532
1533 let mut state = DeploymentState::new("test", "akash1owner");
1534 state.step = Step::SendManifest;
1535 state.lease_id = Some(LeaseId {
1536 owner: "akash1owner".to_string(),
1537 dseq: 123456,
1538 gseq: 1,
1539 oseq: 1,
1540 provider: "akash1provider".to_string(),
1541 });
1542 state.cert_pem = Some(vec![1, 2, 3]);
1543 state.sdl_content = Some(SIMPLE_SDL.to_string());
1544 let result = workflow.advance(&mut state).await;
1547 assert!(result.is_err());
1548 }
1549
1550 #[tokio::test]
1551 async fn test_workflow_send_manifest_missing_sdl() {
1552 let backend = MockBackend::new();
1553 let signer = MockSigner;
1554 let config = WorkflowConfig::default();
1555 let workflow = DeploymentWorkflow::new(&backend, &signer, config);
1556
1557 let mut state = DeploymentState::new("test", "akash1owner");
1558 state.step = Step::SendManifest;
1559 state.lease_id = Some(LeaseId {
1560 owner: "akash1owner".to_string(),
1561 dseq: 123456,
1562 gseq: 1,
1563 oseq: 1,
1564 provider: "akash1provider".to_string(),
1565 });
1566 state.cert_pem = Some(vec![1, 2, 3]);
1567 state.key_pem = Some(vec![4, 5, 6]);
1568 let result = workflow.advance(&mut state).await;
1571 assert!(result.is_err());
1572 }
1573
1574 #[tokio::test]
1575 async fn test_workflow_wait_for_endpoints_missing_key() {
1576 let backend = MockBackend::new();
1577 let signer = MockSigner;
1578 let config = WorkflowConfig::default();
1579 let workflow = DeploymentWorkflow::new(&backend, &signer, config);
1580
1581 let mut state = DeploymentState::new("test", "akash1owner");
1582 state.step = Step::WaitForEndpoints { attempts: 0 };
1583 state.lease_id = Some(LeaseId {
1584 owner: "akash1owner".to_string(),
1585 dseq: 123456,
1586 gseq: 1,
1587 oseq: 1,
1588 provider: "akash1provider".to_string(),
1589 });
1590 state.cert_pem = Some(vec![1, 2, 3]);
1591 let result = workflow.advance(&mut state).await;
1594 assert!(result.is_err());
1595 }
1596
1597 #[tokio::test]
1598 async fn test_workflow_wait_for_endpoints_timeout() {
1599 let backend = MockBackend::new();
1600 backend.set_provider_status(ProviderLeaseStatus {
1602 ready: false,
1603 endpoints: vec![],
1604 });
1605
1606 let signer = MockSigner;
1607 let mut config = WorkflowConfig::default();
1608 config.max_endpoint_wait_attempts = 1; let workflow = DeploymentWorkflow::new(&backend, &signer, config);
1610
1611 let mut state = DeploymentState::new("test", "akash1owner");
1612 state.step = Step::WaitForEndpoints { attempts: 0 };
1613 state.lease_id = Some(LeaseId {
1614 owner: "akash1owner".to_string(),
1615 dseq: 123456,
1616 gseq: 1,
1617 oseq: 1,
1618 provider: "akash1provider".to_string(),
1619 });
1620 state.cert_pem = Some(vec![1, 2, 3]);
1621 state.key_pem = Some(vec![4, 5, 6]);
1622
1623 let result = workflow.advance(&mut state).await.unwrap();
1625 assert!(matches!(result, StepResult::Continue));
1626 assert!(matches!(state.step, Step::WaitForEndpoints { attempts: 1 }));
1627
1628 let result = workflow.advance(&mut state).await.unwrap();
1630 assert!(matches!(result, StepResult::Failed(_)));
1631 }
1632
1633 #[tokio::test]
1634 async fn test_workflow_wait_for_bids_missing_dseq() {
1635 let backend = MockBackend::new();
1636 let signer = MockSigner;
1637 let config = WorkflowConfig::default();
1638 let workflow = DeploymentWorkflow::new(&backend, &signer, config);
1639
1640 let mut state = DeploymentState::new("test", "akash1owner");
1641 state.step = Step::WaitForBids { waited_blocks: 0 };
1642 let result = workflow.advance(&mut state).await;
1645 assert!(result.is_err());
1646 }
1647
1648 #[tokio::test]
1649 async fn test_workflow_create_deployment_missing_sdl() {
1650 let backend = MockBackend::new();
1651 let signer = MockSigner;
1652 let config = WorkflowConfig::default();
1653 let workflow = DeploymentWorkflow::new(&backend, &signer, config);
1654
1655 let mut state = DeploymentState::new("test", "akash1owner");
1656 state.step = Step::CreateDeployment;
1657 let result = workflow.advance(&mut state).await;
1660 assert!(result.is_err());
1661 }
1662
1663 #[test]
1664 fn test_build_manifest_empty_sdl() {
1665 let result = build_manifest("akash1owner", "", 123);
1666 assert!(result.is_err());
1667 assert!(matches!(result.unwrap_err(), DeployError::Manifest(_)));
1668 }
1669}