relentless_http/
command.rs

1use std::marker::PhantomData;
2
3use bytes::Bytes;
4use http_body::Body;
5use relentless::interface::command::{Assault, Relentless};
6use serde::{Deserialize, Serialize};
7
8use crate::{evaluate::HttpResponse, factory::HttpRequest, record::HttpIoRecorder};
9
10#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
11pub struct HttpAssault<ReqB, ResB> {
12    relentless: Relentless,
13    phantom: PhantomData<(ReqB, ResB)>,
14}
15impl<ReqB, ResB> Assault<http::Request<ReqB>, http::Response<ResB>> for HttpAssault<ReqB, ResB>
16where
17    ReqB: Body + From<Bytes> + Default,
18    ResB: Body + From<Bytes> + Default,
19    ResB::Error: std::error::Error + Send + Sync + 'static,
20{
21    type Request = HttpRequest;
22    type Response = HttpResponse;
23    type Recorder = HttpIoRecorder;
24
25    fn command(&self) -> &Relentless {
26        &self.relentless
27    }
28    fn recorder(&self) -> Self::Recorder {
29        HttpIoRecorder
30    }
31}
32
33impl<ReqB, ResB> HttpAssault<ReqB, ResB> {
34    pub fn new(relentless: Relentless) -> Self {
35        Self { relentless, phantom: PhantomData }
36    }
37}
38
39#[cfg(test)]
40#[cfg(all(feature = "yaml", feature = "json"))]
41mod tests {
42    use relentless::{
43        assault::{
44            destinations::{AllOr, Destinations},
45            evaluator::json::JsonEvaluator,
46        },
47        interface::config::{Config, Format, Setting, Severity, Testcase, WorkerConfig},
48    };
49
50    use crate::{
51        evaluate::{HttpBody, HttpHeaders, HttpResponse},
52        factory::HttpRequest,
53    };
54
55    #[test]
56    fn test_config_roundtrip() {
57        let example = Config {
58            worker_config: WorkerConfig {
59                name: Some("example".to_string()),
60                setting: Setting {
61                    request: HttpRequest::default(),
62                    response: HttpResponse { header: HttpHeaders::Ignore, ..Default::default() },
63                    ..Default::default()
64                },
65                ..Default::default()
66            },
67            testcases: vec![Testcase {
68                description: Some("test description".to_string()),
69                target: "/information".to_string(),
70                setting: Setting {
71                    request: HttpRequest::default(),
72                    allow: Some(true),
73                    response: HttpResponse {
74                        body: HttpBody::Json(JsonEvaluator {
75                            ignore: vec!["/datetime".to_string()],
76                            // patch: Some(PatchTo::All(
77                            //     serde_json::from_value(
78                            //         serde_json::json!([{"op": "replace", "path": "/datetime", "value": "2021-01-01"}]),
79                            //     )
80                            //     .unwrap(),
81                            // )),
82                            patch: Some(AllOr::Destinations(Destinations::from_iter([
83                                (
84                                    "actual".to_string(),
85                                    serde_json::from_value(serde_json::json!([{"op": "remove", "path": "/datetime"}]))
86                                        .unwrap(),
87                                ),
88                                (
89                                    "expect".to_string(),
90                                    serde_json::from_value(serde_json::json!([{"op": "remove", "path": "/datetime"}]))
91                                        .unwrap(),
92                                ),
93                            ]))),
94                            patch_fail: Some(Severity::Deny),
95                        }),
96                        ..Default::default()
97                    },
98                    ..Default::default()
99                },
100            }],
101        };
102        let yaml = serde_yaml::to_string(&example).unwrap();
103        // println!("{}", yaml);
104
105        let round_trip = Config::read_str(&yaml, Format::Yaml).unwrap();
106        assert_eq!(example, round_trip);
107    }
108
109    #[test]
110    fn test_config_json_patch() {
111        let all_yaml = r#"
112        name: json patch to all
113        destinations:
114          actual: http://localhost:3000
115          expect: http://localhost:3000
116        testcases:
117        - description: test description
118          target: /information
119          setting:
120            response:
121              body:
122                json:
123                  patch:
124                  - op: replace
125                    path: /datetime
126                    value: 2021-01-01
127        "#;
128        let config = Config::read_str(all_yaml, Format::Yaml).unwrap();
129        assert_eq!(
130            config.testcases[0].setting,
131            Setting {
132                request: HttpRequest::default(),
133                response: HttpResponse {
134                    body: HttpBody::Json(JsonEvaluator {
135                        ignore: vec![],
136                        patch: Some(AllOr::All(
137                            serde_json::from_value(
138                                serde_json::json!([{"op": "replace", "path": "/datetime", "value": "2021-01-01"}])
139                            )
140                            .unwrap(),
141                        )),
142                        patch_fail: None,
143                    }),
144                    ..Default::default()
145                },
146                ..Default::default()
147            },
148        );
149
150        let destinations_yaml = r#"
151        name: json patch to destinations
152        destinations:
153          actual: http://localhost:3000
154          expect: http://localhost:3000
155        testcases:
156        - description: test description
157          target: /information
158          setting:
159            response:
160              body:
161                json:
162                  patch:
163                    actual:
164                    - op: remove
165                      path: /datetime
166                    expect:
167                    - op: remove
168                      path: /datetime
169                  patch-fail: warn
170        "#;
171        let config = Config::read_str(destinations_yaml, Format::Yaml).unwrap();
172        assert_eq!(
173            config.testcases[0].setting,
174            Setting {
175                request: HttpRequest::default(),
176                response: HttpResponse {
177                    body: HttpBody::Json(JsonEvaluator {
178                        ignore: vec![],
179                        patch: Some(AllOr::Destinations(Destinations::from_iter([
180                            (
181                                "actual".to_string(),
182                                serde_json::from_value(serde_json::json!([{"op": "remove", "path": "/datetime"}]))
183                                    .unwrap(),
184                            ),
185                            (
186                                "expect".to_string(),
187                                serde_json::from_value(serde_json::json!([{"op": "remove", "path": "/datetime"}]))
188                                    .unwrap(),
189                            ),
190                        ]))),
191                        patch_fail: Some(Severity::Warn),
192                    }),
193                    ..Default::default()
194                },
195                ..Default::default()
196            },
197        );
198    }
199}