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(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 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}