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, O>(
39        &mut self,
40        ledger: &mut ResourceLedger<C>,
41        result: &TransactionResult<C, O>,
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 earlier open command succeeded after later graph work.
101    pub fn open_succeeds_later<C: Clone>(
102        &mut self,
103        ledger: &mut ResourceLedger<C>,
104        resource_key: ResourceKey,
105        scope: ScopeId,
106        command_revision: Revision,
107    ) -> FakeHostEvent {
108        self.open_succeeded(ledger, resource_key, scope, command_revision)
109    }
110
111    /// Reports that an open command failed.
112    pub fn open_failed<C: Clone>(
113        &mut self,
114        ledger: &mut ResourceLedger<C>,
115        resource_key: ResourceKey,
116        scope: ScopeId,
117        command_revision: Revision,
118        reason: impl Into<String>,
119    ) -> FakeHostEvent {
120        self.observe_outcome(
121            ledger,
122            resource_key,
123            scope,
124            command_revision,
125            HostResourceOutcome::Failed(reason.into()),
126        )
127    }
128
129    /// Reports that a close command succeeded.
130    pub fn close_succeeded<C: Clone>(
131        &mut self,
132        ledger: &mut ResourceLedger<C>,
133        resource_key: ResourceKey,
134        scope: ScopeId,
135        command_revision: Revision,
136    ) -> FakeHostEvent {
137        self.observe_outcome(
138            ledger,
139            resource_key,
140            scope,
141            command_revision,
142            HostResourceOutcome::Closed,
143        )
144    }
145
146    /// Reports that a close command failed at the host boundary.
147    pub fn close_failed<C: Clone>(
148        &mut self,
149        ledger: &mut ResourceLedger<C>,
150        resource_key: ResourceKey,
151        scope: ScopeId,
152        command_revision: Revision,
153        reason: impl Into<String>,
154    ) -> FakeHostEvent {
155        self.observe_outcome(
156            ledger,
157            resource_key,
158            scope,
159            command_revision,
160            HostResourceOutcome::Failed(reason.into()),
161        )
162    }
163
164    /// Reports that a resource was externally lost outside graph propagation.
165    pub fn resource_lost<C: Clone>(
166        &mut self,
167        ledger: &mut ResourceLedger<C>,
168        resource_key: ResourceKey,
169        scope: ScopeId,
170        command_revision: Revision,
171        reason: impl Into<String>,
172    ) -> FakeHostEvent {
173        self.observe_outcome(
174            ledger,
175            resource_key,
176            scope,
177            command_revision,
178            HostResourceOutcome::Failed(reason.into()),
179        )
180    }
181
182    /// Re-delivers a previous host status without assigning a new host revision.
183    pub fn duplicate_status<C: Clone>(
184        &mut self,
185        ledger: &mut ResourceLedger<C>,
186        event: &FakeHostEvent,
187    ) -> FakeHostEvent {
188        let status = event.status.clone();
189        let class = ledger.classify_status(status.clone());
190        FakeHostEvent { status, class }
191    }
192
193    fn status_for_command<C: Clone>(
194        &mut self,
195        ledger: &mut ResourceLedger<C>,
196        command: &ResourceCommand<C>,
197        revision: Revision,
198    ) -> FakeHostEvent {
199        match command {
200            ResourceCommand::Open { key, scope, .. }
201            | ResourceCommand::Replace { key, scope, .. }
202            | ResourceCommand::Refresh { key, scope, .. } => {
203                self.open_succeeded(ledger, key.clone(), *scope, revision)
204            }
205            ResourceCommand::Close { key, scope } => {
206                self.close_succeeded(ledger, key.clone(), *scope, revision)
207            }
208        }
209    }
210
211    fn next_revision(&mut self) -> Revision {
212        let revision = Revision::new(self.next_status_revision);
213        self.next_status_revision += 1;
214        revision
215    }
216}
217
218impl Default for FakeHost {
219    fn default() -> Self {
220        Self::new()
221    }
222}