Skip to main content

vynil_core/
http_mock.rs

1//! Mock HTTP client for tests — Rhai-compatible drop-in for [`crate::http::RestClient`].
2//!
3//! Configure expected `path`/`method` → `return_obj` mappings and register via
4//! `httpmock_rhai_register` instead of `crate::http::http_rhai_register`.
5
6use crate::RhaiRes;
7use rhai::{Dynamic, Engine, Map};
8use serde::{Deserialize, Serialize};
9
10#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Default)]
11pub enum HttpMethod {
12    #[default]
13    Get,
14    Head,
15    Delete,
16    Patch,
17    Post,
18    Put,
19}
20
21#[derive(Deserialize, Serialize, Clone, Debug)]
22pub struct HttpMockItem {
23    pub path: String,
24    pub method: HttpMethod,
25    pub return_obj: Map,
26}
27
28/// In-memory mock that answers `get`/`post`/… from a pre-configured list. See module docs.
29#[derive(Clone, Debug)]
30pub struct RestClientMock {
31    baseurl: String,
32    mocks: Vec<HttpMockItem>,
33}
34impl RestClientMock {
35    #[must_use]
36    pub fn new(base: &str, mocks: Vec<HttpMockItem>) -> Self {
37        Self {
38            baseurl: base.to_string(),
39            mocks,
40        }
41    }
42
43    pub fn set_server_ca(&mut self, _ca: &str) {}
44
45    pub fn set_mtls(&mut self, _cert: &str, _key: &str) {}
46
47    pub fn headers_reset(&mut self) {}
48
49    pub fn add_header(&mut self, _key: String, _value: String) {}
50
51    pub fn add_header_json(&mut self) {}
52
53    pub fn add_header_bearer(&mut self, _token: &str) {}
54
55    pub fn add_header_basic(&mut self, _username: &str, _password: &str) {}
56
57    pub fn baseurl(&mut self, base: String) {
58        self.baseurl = base.to_string();
59    }
60
61    pub fn get(&mut self, path: String) -> RhaiRes<Map> {
62        let found: Vec<HttpMockItem> = self
63            .mocks
64            .clone()
65            .into_iter()
66            .filter(|m| m.method == HttpMethod::Get && m.path == path)
67            .collect();
68        if !found.is_empty() {
69            Ok(found[0].clone().return_obj)
70        } else {
71            Err(format!("Failed to find GET {path} in the Mock database").into())
72        }
73    }
74
75    pub fn head(&mut self, path: String) -> RhaiRes<Map> {
76        let found: Vec<HttpMockItem> = self
77            .mocks
78            .clone()
79            .into_iter()
80            .filter(|m| m.method == HttpMethod::Head && m.path == path)
81            .collect();
82        if !found.is_empty() {
83            Ok(found[0].clone().return_obj)
84        } else {
85            Err(format!("Failed to find HEAD {path} in the Mock database").into())
86        }
87    }
88
89    pub fn patch(&mut self, path: String, _val: Dynamic) -> RhaiRes<Map> {
90        let found: Vec<HttpMockItem> = self
91            .mocks
92            .clone()
93            .into_iter()
94            .filter(|m| m.method == HttpMethod::Patch && m.path == path)
95            .collect();
96        if !found.is_empty() {
97            Ok(found[0].clone().return_obj)
98        } else {
99            Err(format!("Failed to find PATCH {path} in the Mock database").into())
100        }
101    }
102
103    pub fn put(&mut self, path: String, _val: Dynamic) -> RhaiRes<Map> {
104        let found: Vec<HttpMockItem> = self
105            .mocks
106            .clone()
107            .into_iter()
108            .filter(|m| m.method == HttpMethod::Put && m.path == path)
109            .collect();
110        if !found.is_empty() {
111            Ok(found[0].clone().return_obj)
112        } else {
113            Err(format!("Failed to find PUT {path} in the Mock database").into())
114        }
115    }
116
117    pub fn post(&mut self, path: String, _val: Dynamic) -> RhaiRes<Map> {
118        let found: Vec<HttpMockItem> = self
119            .mocks
120            .clone()
121            .into_iter()
122            .filter(|m| m.method == HttpMethod::Post && m.path == path)
123            .collect();
124        if !found.is_empty() {
125            Ok(found[0].clone().return_obj)
126        } else {
127            Err(format!("Failed to find POST {path} in the Mock database").into())
128        }
129    }
130
131    pub fn post_form(&mut self, path: String, _val: Map) -> RhaiRes<Map> {
132        let found: Vec<HttpMockItem> = self
133            .mocks
134            .clone()
135            .into_iter()
136            .filter(|m| m.method == HttpMethod::Post && m.path == path)
137            .collect();
138        if !found.is_empty() {
139            Ok(found[0].clone().return_obj)
140        } else {
141            Err(format!("Failed to find POST {path} in the Mock database").into())
142        }
143    }
144
145    pub fn delete(&mut self, path: String) -> RhaiRes<Map> {
146        let found: Vec<HttpMockItem> = self
147            .mocks
148            .clone()
149            .into_iter()
150            .filter(|m| m.method == HttpMethod::Delete && m.path == path)
151            .collect();
152        if !found.is_empty() {
153            Ok(found[0].clone().return_obj)
154        } else {
155            Err(format!("Failed to find DELETE {path} in the Mock database").into())
156        }
157    }
158
159    pub fn delete_with_body(&mut self, path: String, _val: Dynamic) -> RhaiRes<Map> {
160        let found: Vec<HttpMockItem> = self
161            .mocks
162            .clone()
163            .into_iter()
164            .filter(|m| m.method == HttpMethod::Delete && m.path == path)
165            .collect();
166        if !found.is_empty() {
167            Ok(found[0].clone().return_obj)
168        } else {
169            Err(format!("Failed to find DELETE {path} in the Mock database").into())
170        }
171    }
172}
173
174pub fn httpmock_rhai_register(engine: &mut Engine, mocks: Vec<HttpMockItem>) {
175    let mocks = mocks.clone();
176    let new = move |base: &str| -> RestClientMock { RestClientMock::new(base, mocks.clone()) };
177    engine
178        .register_type_with_name::<RestClientMock>("RestClient")
179        .register_fn("new_http_client", new)
180        .register_fn("headers_reset", RestClientMock::headers_reset)
181        .register_fn("set_baseurl", RestClientMock::baseurl)
182        .register_fn("set_server_ca", RestClientMock::set_server_ca)
183        .register_fn("set_mtls_cert_key", RestClientMock::set_mtls)
184        .register_fn("add_header", RestClientMock::add_header)
185        .register_fn("add_header_json", RestClientMock::add_header_json)
186        .register_fn("add_header_bearer", RestClientMock::add_header_bearer)
187        .register_fn("add_header_basic", RestClientMock::add_header_basic)
188        .register_fn("head", RestClientMock::head)
189        .register_fn("get", RestClientMock::get)
190        .register_fn("http_get", RestClientMock::get)
191        .register_fn("delete", RestClientMock::delete)
192        .register_fn("http_delete", RestClientMock::delete)
193        .register_fn("delete_with_body", RestClientMock::delete_with_body)
194        .register_fn("http_delete_with_body", RestClientMock::delete_with_body)
195        .register_fn("patch", RestClientMock::patch)
196        .register_fn("http_patch", RestClientMock::patch)
197        .register_fn("post", RestClientMock::post)
198        .register_fn("http_post", RestClientMock::post)
199        .register_fn("put", RestClientMock::put)
200        .register_fn("http_put", RestClientMock::put)
201        .register_fn("post_form", RestClientMock::post_form)
202        .register_fn("http_post_form", RestClientMock::post_form)
203        .register_fn("headers_get", crate::http::headers_get)
204        .register_fn("headers_has", crate::http::headers_has);
205}