lenso_capability_http_endpoint/
testing.rs1use std::{error::Error, fmt};
4
5use lenso_kernel::{CancellationToken, InvocationContext, RuntimeFailure};
6use serde::{Serialize, de::DeserializeOwned};
7
8use crate::{
9 EndpointHandleInvocationError, EndpointProvider, HandleError, HandleRequest,
10 HandleRequestHeadersItem, HandleRequestPathParametersItem, HandleResponse, HttpEndpoint,
11 response::StatusCode,
12};
13
14#[derive(Debug)]
16pub struct EndpointTest<P> {
17 provider: P,
18}
19
20impl<P> EndpointTest<P>
21where
22 P: HttpEndpoint,
23{
24 #[must_use]
26 pub fn new(provider: P) -> Self {
27 Self { provider }
28 }
29
30 #[must_use]
32 pub fn request(&self, route_id: impl Into<String>) -> TestRequest<'_, P> {
33 TestRequest {
34 provider: &self.provider,
35 route_id: route_id.into(),
36 body: Vec::new(),
37 headers: Vec::new(),
38 path_parameters: Vec::new(),
39 query: None,
40 }
41 }
42}
43
44#[derive(Debug)]
46pub struct TestRequest<'a, P> {
47 provider: &'a P,
48 route_id: String,
49 body: Vec<u8>,
50 headers: Vec<HandleRequestHeadersItem>,
51 path_parameters: Vec<HandleRequestPathParametersItem>,
52 query: Option<String>,
53}
54
55impl<P> TestRequest<'_, P>
56where
57 P: HttpEndpoint,
58{
59 pub fn json(mut self, value: &impl Serialize) -> Result<Self, serde_json::Error> {
61 self.body = serde_json::to_vec(value)?;
62 self.headers.push(HandleRequestHeadersItem {
63 name: "content-type".to_owned(),
64 value: "application/json".to_owned(),
65 });
66 Ok(self)
67 }
68
69 pub fn query(mut self, value: &impl Serialize) -> Result<Self, serde_urlencoded::ser::Error> {
71 self.query = Some(serde_urlencoded::to_string(value)?);
72 Ok(self)
73 }
74
75 #[must_use]
77 pub fn path_parameter(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
78 self.path_parameters.push(HandleRequestPathParametersItem {
79 name: name.into(),
80 value: value.into(),
81 });
82 self
83 }
84
85 #[must_use]
87 pub fn header(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
88 self.headers.push(HandleRequestHeadersItem {
89 name: name.into(),
90 value: value.into(),
91 });
92 self
93 }
94
95 pub async fn send(self) -> Result<TestResponse, EndpointTestError> {
97 let route = P::ROUTES
98 .iter()
99 .find(|route| route.route_id() == self.route_id)
100 .ok_or_else(|| EndpointTestError::UnknownRoute(self.route_id.clone()))?;
101 let path = self
102 .path_parameters
103 .iter()
104 .fold(route.path().to_owned(), |path, parameter| {
105 path.replace(&format!("{{{}}}", parameter.name), ¶meter.value)
106 });
107 let request = HandleRequest {
108 body: self.body.into(),
109 credential: None,
110 headers: self.headers,
111 method: route.method().to_owned(),
112 path,
113 path_parameters: self.path_parameters,
114 query: self.query,
115 request_id: "endpoint-test-1".to_owned(),
116 route_id: self.route_id,
117 };
118 let context = InvocationContext::new(1, None, CancellationToken::new());
119 let response = self
120 .provider
121 .handle(context, request)
122 .await
123 .map_err(EndpointTestError::Runtime)?
124 .map_err(EndpointTestError::Domain)?;
125 Ok(TestResponse(response))
126 }
127}
128
129#[derive(Clone, Debug)]
131pub struct TestResponse(HandleResponse);
132
133impl TestResponse {
134 #[must_use]
136 pub fn status(&self) -> StatusCode {
137 u16::try_from(self.0.status)
138 .ok()
139 .and_then(|status| StatusCode::from_u16(status).ok())
140 .expect("an Endpoint response must contain a valid HTTP status")
141 }
142
143 pub fn json<T>(&self) -> Result<T, serde_json::Error>
145 where
146 T: DeserializeOwned,
147 {
148 serde_json::from_slice(&self.0.body)
149 }
150
151 #[must_use]
153 pub fn header(&self, name: &str) -> Option<&str> {
154 self.0
155 .headers
156 .iter()
157 .find(|header| header.name.eq_ignore_ascii_case(name))
158 .map(|header| header.value.as_str())
159 }
160
161 #[must_use]
163 pub fn into_inner(self) -> HandleResponse {
164 self.0
165 }
166}
167
168#[derive(Debug)]
170pub enum EndpointTestError {
171 UnknownRoute(String),
173 Domain(HandleError),
175 Runtime(RuntimeFailure),
177}
178
179impl fmt::Display for EndpointTestError {
180 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
181 match self {
182 Self::UnknownRoute(route_id) => {
183 write!(formatter, "unknown Endpoint route `{route_id}`")
184 }
185 Self::Domain(error) => write!(formatter, "Endpoint domain error: {error:?}"),
186 Self::Runtime(error) => write!(formatter, "Endpoint runtime failure: {error:?}"),
187 }
188 }
189}
190
191impl Error for EndpointTestError {}
192
193impl From<EndpointHandleInvocationError> for EndpointTestError {
194 fn from(error: EndpointHandleInvocationError) -> Self {
195 match error {
196 EndpointHandleInvocationError::Domain(error) => Self::Domain(error),
197 EndpointHandleInvocationError::Runtime(error) => Self::Runtime(error),
198 }
199 }
200}