cloud_sdk_testkit/
script.rs1use crate::{
4 ActionState, DynamicRequest, FixtureKind, MAX_DYNAMIC_RECORDS, ProviderFixtureBuilder,
5 ResponseFixture,
6};
7
8#[derive(Clone, Copy, Debug, Eq, PartialEq)]
10pub enum ScenarioScriptError {
11 Empty,
13 TooManySteps,
15 ExpectedPagination,
17 InvalidPageSequence,
19 PaginationMetadataChanged,
21 PaginationDidNotFinish,
23 ExpectedAction,
25 ActionProgressDecreased,
27 ActionFinishedEarly,
29 ActionDidNotFinish,
31 Exhausted,
33}
34
35impl_static_error!(ScenarioScriptError,
36 Self::Empty => "scenario script is empty",
37 Self::TooManySteps => "scenario script exceeds the step limit",
38 Self::ExpectedPagination => "pagination script contains a different fixture kind",
39 Self::InvalidPageSequence => "pagination script page sequence is invalid",
40 Self::PaginationMetadataChanged => "pagination script metadata changes between pages",
41 Self::PaginationDidNotFinish => "pagination script does not finish on the last page",
42 Self::ExpectedAction => "action script contains a different fixture kind",
43 Self::ActionProgressDecreased => "action script progress decreases",
44 Self::ActionFinishedEarly => "action script finishes before its final step",
45 Self::ActionDidNotFinish => "action script does not finish",
46 Self::Exhausted => "scenario script has no response remaining",
47);
48
49pub struct PaginationScript<'fixture> {
51 fixtures: &'fixture [ResponseFixture<'fixture>],
52}
53
54impl<'fixture> PaginationScript<'fixture> {
55 pub fn new(
57 fixtures: &'fixture [ResponseFixture<'fixture>],
58 ) -> Result<Self, ScenarioScriptError> {
59 validate_length(fixtures)?;
60 let first = fixtures
61 .first()
62 .and_then(ResponseFixture::pagination)
63 .ok_or(ScenarioScriptError::ExpectedPagination)?;
64 let mut expected_page = 1_u64;
65 for fixture in fixtures {
66 if fixture.kind() != FixtureKind::Pagination {
67 return Err(ScenarioScriptError::ExpectedPagination);
68 }
69 let page = fixture
70 .pagination()
71 .ok_or(ScenarioScriptError::ExpectedPagination)?;
72 if page.page() != expected_page {
73 return Err(ScenarioScriptError::InvalidPageSequence);
74 }
75 if page.per_page() != first.per_page()
76 || page.total_entries() != first.total_entries()
77 || page.last_page() != first.last_page()
78 {
79 return Err(ScenarioScriptError::PaginationMetadataChanged);
80 }
81 expected_page = expected_page
82 .checked_add(1)
83 .ok_or(ScenarioScriptError::InvalidPageSequence)?;
84 }
85 let last = fixtures
86 .last()
87 .and_then(ResponseFixture::pagination)
88 .ok_or(ScenarioScriptError::ExpectedPagination)?;
89 if last.page() != last.last_page() {
90 return Err(ScenarioScriptError::PaginationDidNotFinish);
91 }
92 Ok(Self { fixtures })
93 }
94
95 #[must_use]
97 pub const fn len(&self) -> usize {
98 self.fixtures.len()
99 }
100
101 #[must_use]
103 pub const fn is_empty(&self) -> bool {
104 self.fixtures.is_empty()
105 }
106}
107
108impl<'fixture> ProviderFixtureBuilder<'fixture> for PaginationScript<'fixture> {
109 type Error = ScenarioScriptError;
110
111 fn build<'request>(
112 &self,
113 request: DynamicRequest<'request>,
114 ) -> Result<&'fixture ResponseFixture<'fixture>, Self::Error> {
115 self.fixtures
116 .get(request.sequence())
117 .ok_or(ScenarioScriptError::Exhausted)
118 }
119}
120
121pub struct ActionScript<'fixture> {
123 fixtures: &'fixture [ResponseFixture<'fixture>],
124}
125
126impl<'fixture> ActionScript<'fixture> {
127 pub fn new(
129 fixtures: &'fixture [ResponseFixture<'fixture>],
130 ) -> Result<Self, ScenarioScriptError> {
131 validate_length(fixtures)?;
132 let mut previous_progress = 0_u8;
133 let final_index = fixtures
134 .len()
135 .checked_sub(1)
136 .ok_or(ScenarioScriptError::Empty)?;
137 for (index, fixture) in fixtures.iter().enumerate() {
138 if fixture.kind() != FixtureKind::Action {
139 return Err(ScenarioScriptError::ExpectedAction);
140 }
141 let action = fixture
142 .action_metadata()
143 .ok_or(ScenarioScriptError::ExpectedAction)?;
144 if action.progress() < previous_progress {
145 return Err(ScenarioScriptError::ActionProgressDecreased);
146 }
147 let terminal = !matches!(action.state(), ActionState::Running);
148 if terminal && index != final_index {
149 return Err(ScenarioScriptError::ActionFinishedEarly);
150 }
151 if !terminal && index == final_index {
152 return Err(ScenarioScriptError::ActionDidNotFinish);
153 }
154 previous_progress = action.progress();
155 }
156 Ok(Self { fixtures })
157 }
158
159 #[must_use]
161 pub const fn len(&self) -> usize {
162 self.fixtures.len()
163 }
164
165 #[must_use]
167 pub const fn is_empty(&self) -> bool {
168 self.fixtures.is_empty()
169 }
170}
171
172impl<'fixture> ProviderFixtureBuilder<'fixture> for ActionScript<'fixture> {
173 type Error = ScenarioScriptError;
174
175 fn build<'request>(
176 &self,
177 request: DynamicRequest<'request>,
178 ) -> Result<&'fixture ResponseFixture<'fixture>, Self::Error> {
179 self.fixtures
180 .get(request.sequence())
181 .ok_or(ScenarioScriptError::Exhausted)
182 }
183}
184
185fn validate_length(fixtures: &[ResponseFixture<'_>]) -> Result<(), ScenarioScriptError> {
186 if fixtures.is_empty() {
187 return Err(ScenarioScriptError::Empty);
188 }
189 if fixtures.len() > MAX_DYNAMIC_RECORDS {
190 return Err(ScenarioScriptError::TooManySteps);
191 }
192 Ok(())
193}