Skip to main content

trellis_testing/
host.rs

1use trellis_core::{
2    HostResourceOutcome, ResourceCommand, ResourceKey, Revision, ScopeId, TransactionResult,
3};
4
5use crate::{HostStatusClass, HostStatusEvent, ResourceLedger};
6
7/// Status event produced by the fake host boundary.
8#[derive(Clone, Debug, Eq, PartialEq)]
9pub struct FakeHostEvent {
10    /// Explicit event that application tests feed back as canonical input.
11    pub status: HostStatusEvent,
12    /// Ledger classification for the event at the time it was produced.
13    pub class: HostStatusClass,
14}
15
16impl FakeHostEvent {
17    /// Consumes the event into the host status application tests feed as input.
18    pub fn into_status(self) -> HostStatusEvent {
19        self.status
20    }
21}
22
23/// Deterministic fake host boundary for resource status simulations.
24#[derive(Clone, Debug, Eq, PartialEq)]
25pub struct FakeHost {
26    next_status_revision: u64,
27}
28
29impl FakeHost {
30    /// Creates a fake host with status revisions starting at one.
31    pub const fn new() -> Self {
32        Self {
33            next_status_revision: 1,
34        }
35    }
36
37    /// Applies a transaction result to the ledger and returns explicit statuses.
38    pub fn apply_result<C: Clone>(
39        &mut self,
40        ledger: &mut ResourceLedger<C>,
41        result: &TransactionResult<C>,
42    ) -> Vec<FakeHostEvent> {
43        ledger.apply_result(result);
44        result
45            .resource_plan
46            .commands()
47            .iter()
48            .map(|command| self.status_for_command(ledger, command, result.revision))
49            .collect()
50    }
51
52    /// Produces a custom successful-open status event.
53    pub fn observe<C: Clone>(
54        &mut self,
55        ledger: &mut ResourceLedger<C>,
56        resource_key: ResourceKey,
57        scope: ScopeId,
58        command_revision: Revision,
59    ) -> FakeHostEvent {
60        self.observe_outcome(
61            ledger,
62            resource_key,
63            scope,
64            command_revision,
65            HostResourceOutcome::Open,
66        )
67    }
68
69    /// Produces a custom host outcome and classifies it through the ledger.
70    pub fn observe_outcome<C: Clone>(
71        &mut self,
72        ledger: &mut ResourceLedger<C>,
73        resource_key: ResourceKey,
74        scope: ScopeId,
75        command_revision: Revision,
76        status: HostResourceOutcome,
77    ) -> FakeHostEvent {
78        let status = HostStatusEvent {
79            resource_key,
80            scope,
81            command_revision,
82            status_revision: self.next_revision(),
83            status,
84        };
85        let class = ledger.classify_status(status.clone());
86        FakeHostEvent { status, class }
87    }
88
89    /// Reports that an open command succeeded.
90    pub fn open_succeeded<C: Clone>(
91        &mut self,
92        ledger: &mut ResourceLedger<C>,
93        resource_key: ResourceKey,
94        scope: ScopeId,
95        command_revision: Revision,
96    ) -> FakeHostEvent {
97        self.observe(ledger, resource_key, scope, command_revision)
98    }
99
100    /// Reports that an open command failed.
101    pub fn open_failed<C: Clone>(
102        &mut self,
103        ledger: &mut ResourceLedger<C>,
104        resource_key: ResourceKey,
105        scope: ScopeId,
106        command_revision: Revision,
107        reason: impl Into<String>,
108    ) -> FakeHostEvent {
109        self.observe_outcome(
110            ledger,
111            resource_key,
112            scope,
113            command_revision,
114            HostResourceOutcome::Failed(reason.into()),
115        )
116    }
117
118    /// Reports that a close command succeeded.
119    pub fn close_succeeded<C: Clone>(
120        &mut self,
121        ledger: &mut ResourceLedger<C>,
122        resource_key: ResourceKey,
123        scope: ScopeId,
124        command_revision: Revision,
125    ) -> FakeHostEvent {
126        self.observe_outcome(
127            ledger,
128            resource_key,
129            scope,
130            command_revision,
131            HostResourceOutcome::Closed,
132        )
133    }
134
135    /// Reports that a close command failed at the host boundary.
136    pub fn close_failed<C: Clone>(
137        &mut self,
138        ledger: &mut ResourceLedger<C>,
139        resource_key: ResourceKey,
140        scope: ScopeId,
141        command_revision: Revision,
142        reason: impl Into<String>,
143    ) -> FakeHostEvent {
144        self.observe_outcome(
145            ledger,
146            resource_key,
147            scope,
148            command_revision,
149            HostResourceOutcome::Failed(reason.into()),
150        )
151    }
152
153    /// Reports that a resource was externally lost outside graph propagation.
154    pub fn resource_lost<C: Clone>(
155        &mut self,
156        ledger: &mut ResourceLedger<C>,
157        resource_key: ResourceKey,
158        scope: ScopeId,
159        command_revision: Revision,
160        reason: impl Into<String>,
161    ) -> FakeHostEvent {
162        self.observe_outcome(
163            ledger,
164            resource_key,
165            scope,
166            command_revision,
167            HostResourceOutcome::Failed(reason.into()),
168        )
169    }
170
171    /// Re-delivers a previous host status without assigning a new host revision.
172    pub fn duplicate_status<C: Clone>(
173        &mut self,
174        ledger: &mut ResourceLedger<C>,
175        event: &FakeHostEvent,
176    ) -> FakeHostEvent {
177        let status = event.status.clone();
178        let class = ledger.classify_status(status.clone());
179        FakeHostEvent { status, class }
180    }
181
182    fn status_for_command<C: Clone>(
183        &mut self,
184        ledger: &mut ResourceLedger<C>,
185        command: &ResourceCommand<C>,
186        revision: Revision,
187    ) -> FakeHostEvent {
188        match command {
189            ResourceCommand::Open { key, scope, .. }
190            | ResourceCommand::Replace { key, scope, .. }
191            | ResourceCommand::Refresh { key, scope, .. } => {
192                self.open_succeeded(ledger, key.clone(), *scope, revision)
193            }
194            ResourceCommand::Close { key, scope } => {
195                self.close_succeeded(ledger, key.clone(), *scope, revision)
196            }
197        }
198    }
199
200    fn next_revision(&mut self) -> Revision {
201        let revision = Revision::new(self.next_status_revision);
202        self.next_status_revision += 1;
203        revision
204    }
205}
206
207impl Default for FakeHost {
208    fn default() -> Self {
209        Self::new()
210    }
211}