http_test_server/
resource.rs

1//! Server resource builders
2use std::sync::Arc;
3use std::sync::Mutex;
4use std::sync::mpsc;
5use std::sync::atomic::{AtomicBool, Ordering};
6use std::collections::HashMap;
7use std::time::Duration;
8
9use ::Method;
10use ::Status;
11
12use regex::Regex;
13
14/// Responsible for configuring a resource and interacting with it.
15///
16/// Must be created through `TestServer`.
17///
18/// By default a resource's method is `GET` and response is `200 Ok` with empty body.
19///
20/// ```
21/// use http_test_server::TestServer;
22/// use http_test_server::http::{Method, Status};
23///
24/// let server = TestServer::new().unwrap();
25/// let resource = server.create_resource("/i-am-a-resource");
26///
27/// resource
28///     .status(Status::PartialContent)
29///     .method(Method::POST)
30///     .body("All good!");
31///
32/// ```
33///
34/// To create resources with variable parts in the URL, you may use path and query parameters:
35///
36/// ```
37/// # use http_test_server::TestServer;
38/// # use http_test_server::http::{Method, Status};
39/// # let server = TestServer::new().unwrap();
40/// // matches /user/*/details?filter=*
41/// let resource = server.create_resource("/user/{userId}/details?filter=*");
42/// resource.body("All good for {path.userId} with filter {query.filter}!");
43/// ```
44/// _Note: I don't think it's a good idea to write mocks with complex behaviours. Usually,
45///  they are less maintainable and harder to track._
46///
47///  _Instead, I would suggest creating one resource
48///  for each behaviour expected. Having said that, I'm not here to judge. Do whatever floats your boat! :)_
49///
50
51pub struct Resource {
52    uri: String,
53    uri_regex: Regex,
54    params: Arc<Mutex<URIParameters>>,
55    status_code: Arc<Mutex<Status>>,
56    custom_status_code: Arc<Mutex<Option<String>>>,
57    headers: Arc<Mutex<HashMap<String, String>>>,
58    body: Arc<Mutex<Option<&'static str>>>,
59    body_builder: Arc<Mutex<Option<BodyBuilder>>>, // ᕦ(ò_óˇ)ᕤ
60    method: Arc<Mutex<Method>>,
61    delay: Arc<Mutex<Option<Duration>>>,
62    request_count: Arc<Mutex<u32>>,
63    is_stream: Arc<AtomicBool>,
64    stream_listeners: Arc<Mutex<Vec<mpsc::Sender<String>>>>
65}
66
67struct URIParameters {
68    path: Vec<String>,
69    query: HashMap<String, String>
70}
71
72type BodyBuilder = Box<dyn Fn(RequestParameters) -> String + Send>;
73
74impl Resource {
75    pub(crate) fn new(uri: &str) -> Resource {
76        let (uri_regex, params) = create_uri_regex(uri);
77
78        Resource {
79            uri: String::from(uri),
80            uri_regex,
81            params: Arc::new(Mutex::new(params)),
82            status_code: Arc::new(Mutex::new(Status::OK)),
83            custom_status_code: Arc::new(Mutex::new(None)),
84            headers: Arc::new(Mutex::new(HashMap::new())),
85            body: Arc::new(Mutex::new(None)),
86            body_builder: Arc::new(Mutex::new(None)),
87            method: Arc::new(Mutex::new(Method::GET)),
88            delay: Arc::new(Mutex::new(None)),
89            request_count: Arc::new(Mutex::new(0)),
90            is_stream: Arc::new(AtomicBool::new(false)),
91            stream_listeners: Arc::new(Mutex::new(vec!()))
92        }
93    }
94
95    /// Defines response's HTTP Status .
96    ///
97    /// Refer to [`custom_status`] for Statuses not covered by [`Status`].
98    /// ```
99    /// # use http_test_server::TestServer;
100    /// # use http_test_server::http::{Method, Status};
101    /// # let server = TestServer::new().unwrap();
102    /// # let resource = server.create_resource("/i-am-a-resource");
103    /// resource.status(Status::PartialContent);
104    /// ```
105    /// [`custom_status`]: struct.Resource.html#method.custom_status
106    /// [`Status`]: ../http/enum.Status.html
107    pub fn status(&self, status_code: Status) -> &Resource {
108        if let Ok(mut status) = self.status_code.lock() {
109            *status = status_code;
110        }
111
112        if let Ok(mut custom_status) = self.custom_status_code.lock() {
113            *custom_status = None;
114        }
115
116        self
117    }
118
119    fn get_status_description(&self) -> String {
120        match *(self.custom_status_code.lock().unwrap()) {
121            Some(ref custom_status) => custom_status.clone(),
122            None => self.status_code.lock().unwrap().description().to_string()
123        }
124    }
125
126    /// Defines a custom HTTP Status to response.
127    ///
128    /// Use it to return HTTP statuses that are not covered by [`Status`].
129    ///
130    /// ```
131    /// # use http_test_server::TestServer;
132    /// # let server = TestServer::new().unwrap();
133    /// # let resource = server.create_resource("/i-am-a-resource");
134    /// resource.custom_status(333, "Only Half Beast");
135    /// ```
136    /// [`Status`]: ../http/enum.Status.html
137    pub fn custom_status(&self, status_code: u16, description: &str) -> &Resource {
138        if let Ok(mut status) = self.custom_status_code.lock() {
139            *status = Some(format!("{} {}", status_code, description));
140        }
141        self
142    }
143
144    /// Defines response headers.
145    ///
146    /// Call it multiple times to add multiple headers.
147    /// If a header is defined twice only the late value is returned.
148    ///
149    /// ```
150    /// # use http_test_server::TestServer;
151    /// # let server = TestServer::new().unwrap();
152    /// # let resource = server.create_resource("/i-am-a-resource");
153    /// resource
154    ///     .header("Content-Type", "application/json")
155    ///     .header("Connection", "Keep-Alive");
156    /// ```
157    pub fn header(&self, header_name: &str, header_value: &str) -> &Resource {
158        let mut headers = self.headers.lock().unwrap();
159        headers.insert(String::from(header_name), String::from(header_value));
160        self
161    }
162
163    fn get_headers(&self) -> String {
164        let headers = self.headers.lock().unwrap();
165        headers.iter().fold(String::new(), | headers, (name, value) | {
166            headers + &format!("{}: {}\r\n", name, value)
167        })
168    }
169
170    /// Defines query parameters.
171    ///
172    /// ```
173    /// # use http_test_server::TestServer;
174    /// # let server = TestServer::new().unwrap();
175    /// # let resource = server.create_resource("/i-am-a-resource");
176    /// resource
177    ///     .query("filter", "*") // wildcard, matches any value
178    ///     .query("version", "1"); // only matches request with version == 1
179    /// ```
180    /// This is equivalent to:
181    /// ```
182    /// # use http_test_server::TestServer;
183    /// # use http_test_server::http::{Method, Status};
184    /// # let server = TestServer::new().unwrap();
185    /// let resource = server.create_resource("/?filter=*&version=1");
186    /// ```
187    pub fn query(&self, name: &str, value: &str) -> &Resource {
188        let mut params = self.params.lock().unwrap();
189        params.query.insert(String::from(name), String::from(value));
190        self
191    }
192
193    /// Defines response's body.
194    ///
195    /// If the response is a stream this value will be sent straight after connection.
196    ///
197    /// Calling multiple times will overwrite the previous value.
198    ///
199    /// ```
200    /// # use http_test_server::TestServer;
201    /// # let server = TestServer::new().unwrap();
202    /// # let resource = server.create_resource("/i-am-a-resource");
203    /// resource.body("this is important!");
204    /// ```
205    ///
206    /// It's possible to use path and query parameters in the response body by defining `{path.<parameter_name>}` or `{query.<parameter_name>}`:
207    /// ```
208    /// # use http_test_server::TestServer;
209    /// # let server = TestServer::new().unwrap();
210    /// let resource = server.create_resource("/user/{userId}?filter=*");
211    /// resource.body("Response for user: {path.userId} filter: {query.filter}");
212    /// ```
213    pub fn body(&self, content: &'static str) -> &Resource {
214        if self.body_builder.lock().unwrap().is_some() {
215            panic!("You can't define 'body' when 'body_fn' is already defined");
216        }
217
218        if let Ok(mut body) = self.body.lock() {
219            *body = Some(content);
220        }
221
222        self
223    }
224
225    /// Defines function used to build the response's body.
226    ///
227    /// If the response is a stream value will be sent straight after connection.
228    ///
229    /// Calling multiple times will overwrite the previous value.
230    ///
231    /// ```
232    /// # use http_test_server::TestServer;
233    /// # let server = TestServer::new().unwrap();
234    /// let resource = server.create_resource("/character/{id}?version=*");
235    /// resource.body_fn(|params| {
236    ///     println!("version: {}", params.query.get("version").unwrap());
237    ///
238    ///     match params.path.get("id").unwrap().as_str() {
239    ///         "Balrog" => r#"{ "message": "YOU SHALL NOT PASS!" }"#.to_string(),
240    ///         _ => r#"{ "message": "Fly, you fools!" }"#.to_string()
241    ///     }
242    /// });
243    ///
244    /// ```
245    pub fn body_fn(&self, builder: impl Fn(RequestParameters) -> String + Send + 'static) -> &Resource {
246        if self.body.lock().unwrap().is_some() {
247            panic!("You can't define 'body_fn' when 'body' is already defined");
248        }
249
250        if let Ok(mut body_builder) = self.body_builder.lock() {
251            *body_builder = Some(Box::new(builder));
252        }
253
254        self
255    }
256
257    /// Defines HTTP method.
258    ///
259    /// A resource will only respond to one method, however multiple resources with same URL and
260    /// different methods can be created.
261    /// ```
262    /// # use http_test_server::TestServer;
263    /// use http_test_server::http::Method;
264    /// # let server = TestServer::new().unwrap();
265    /// let resource_put = server.create_resource("/i-am-a-resource");
266    /// let resource_post = server.create_resource("/i-am-a-resource");
267    ///
268    /// resource_put.method(Method::PUT);
269    /// resource_post.method(Method::POST);
270    /// ```
271    pub fn method(&self, method: Method) -> &Resource {
272        if let Ok(mut m) = self.method.lock() {
273            *m = method;
274        }
275
276        self
277    }
278
279    pub(crate) fn get_method(&self) -> Method {
280        (*self.method.lock().unwrap()).clone()
281    }
282
283    /// Defines delay to response after client connected
284    /// ```
285    /// # use http_test_server::TestServer;
286    /// use std::time::Duration;
287    /// # let server = TestServer::new().unwrap();
288    /// # let resource = server.create_resource("/i-am-a-resource");
289    ///
290    /// resource.delay(Duration::from_millis(500));
291    /// ```
292    pub fn delay(&self, delay: Duration) -> &Resource {
293        if let Ok(mut d) = self.delay.lock() {
294            *d = Some(delay);
295        }
296
297        self
298    }
299
300    pub(crate) fn get_delay(&self) -> Option<Duration> {
301        *self.delay.lock().unwrap()
302    }
303
304    /// Set response as stream, this means clients won't be disconnected after body is sent and
305    /// updates can be sent and received.
306    ///
307    /// See also: [`send`], [`send_line`], [`stream_receiver`].
308    /// ```
309    /// # use http_test_server::TestServer;
310    /// # let server = TestServer::new().unwrap();
311    /// let resource = server.create_resource("/stream");
312    ///
313    /// resource.stream();
314    ///
315    /// resource
316    ///     .send_line("some")
317    ///     .send_line("data")
318    ///     .close_open_connections();
319    /// ```
320    /// [`send`]: struct.Resource.html#method.send
321    /// [`send_line`]: struct.Resource.html#method.send_line
322    /// [`stream_receiver`]: struct.Resource.html#method.stream_receiver
323    /// [`close_open_connections`]: struct.Resource.html#method.close_open_connections
324    pub fn stream(&self) -> &Resource {
325        self.is_stream.store(true, Ordering::Relaxed);
326
327        self
328    }
329
330    pub(crate) fn is_stream(&self) -> bool {
331        self.is_stream.load(Ordering::Relaxed)
332    }
333
334    fn create_body(&self, uri: &str) -> String {
335        let params = self.extract_params_from_uri(uri);
336
337        if let Some(body_builder) = &*self.body_builder.lock().unwrap() {
338            return body_builder(params);
339        }
340
341        match *self.body.lock().unwrap() {
342            Some(body) => {
343                let mut body = body.to_string();
344
345                for (name, value) in &params.path {
346                    let key = format!("{{path.{}}}", name);
347                    body = body.replace(&key, value);
348                }
349
350                for (name, value) in &params.query {
351                    let key = format!("{{query.{}}}", name);
352                    body = body.replace(&key, value);
353                }
354
355                body
356            },
357            None => {
358                String::from("")
359            }
360        }
361    }
362
363    fn extract_params_from_uri(&self, uri: &str) -> RequestParameters {
364        RequestParameters { path: self.extra_path_params(uri), query: extract_query_params(uri) }
365    }
366
367    fn extra_path_params(&self, uri: &str) -> HashMap<String, String> {
368        let mut params = HashMap::new();
369
370        if let Some(values) = self.uri_regex.captures(uri) {
371            for param in &self.params.lock().unwrap().path {
372                if let Some(value) = values.name(param) {
373                    params.insert(String::from(param), String::from(value.as_str()));
374                }
375            }
376        }
377
378        params
379    }
380
381    pub(crate) fn build_response(&self, uri: &str) -> String {
382        format!("HTTP/1.1 {}\r\n{}\r\n{}",
383            self.get_status_description(),
384            self.get_headers(),
385            self.create_body(uri)
386        )
387    }
388
389    pub(crate) fn increment_request_count(&self) {
390        *(self.request_count.lock().unwrap()) += 1;
391    }
392
393    /// Send data to all connected clients.
394    ///
395    /// See also: [`send_line`], [`stream`].
396    /// ```
397    /// # use http_test_server::TestServer;
398    /// # let server = TestServer::new().unwrap();
399    /// let resource = server.create_resource("/stream");
400    ///
401    /// resource.stream();
402    ///
403    /// resource
404    ///     .send("some")
405    ///     .send(" data");
406    /// ```
407    /// [`send_line`]: struct.Resource.html#method.send_line
408    /// [`stream`]: struct.Resource.html#method.stream
409    pub fn send(&self, data: &str) -> &Resource {
410        if let Ok(mut listeners) = self.stream_listeners.lock() {
411            let mut invalid_listeners = vec!();
412            for (i, listener) in listeners.iter().enumerate() {
413                if listener.send(String::from(data)).is_err() {
414                    invalid_listeners.push(i);
415                }
416            }
417
418            for i in invalid_listeners.iter() {
419                listeners.remove(*i);
420            }
421        }
422
423        self
424    }
425
426    /// Send data to all connected clients.
427    /// Same as [`send`], but appends `\n` to data.
428    ///
429    /// See also: [`stream`]
430    /// ```
431    /// # use http_test_server::TestServer;
432    /// # let server = TestServer::new().unwrap();
433    /// let resource = server.create_resource("/stream");
434    ///
435    /// resource.stream();
436    ///
437    /// resource
438    ///     .send_line("one line")
439    ///     .send_line("another line");
440    /// ```
441    /// [`send`]: struct.Resource.html#method.send
442    /// [`stream`]: struct.Resource.html#method.stream
443    pub fn send_line(&self, data: &str) -> &Resource {
444        self.send(&format!("{}\n", data))
445    }
446
447    /// Close all connections with clients.
448    ///
449    /// See also: [`stream`]
450    /// ```
451    /// # use http_test_server::TestServer;
452    /// # let server = TestServer::new().unwrap();
453    /// let resource = server.create_resource("/stream");
454    ///
455    /// resource.stream();
456    ///
457    /// resource.close_open_connections();
458    /// ```
459    /// [`stream`]: struct.Resource.html#method.stream
460
461    pub fn close_open_connections(&self) {
462        if let Ok(mut listeners) = self.stream_listeners.lock() {
463            listeners.clear();
464        }
465    }
466
467    /// Number of clients connected to stream.
468    ///
469    /// See also: [`stream`]
470    /// ```
471    /// # use http_test_server::TestServer;
472    /// # let server = TestServer::new().unwrap();
473    /// let resource = server.create_resource("/stream");
474    ///
475    /// resource
476    ///     .stream()
477    ///     .close_open_connections();
478    ///
479    /// assert_eq!(resource.open_connections_count(), 0);
480    /// ```
481    /// [`stream`]: struct.Resource.html#method.stream
482    pub fn open_connections_count(&self) -> usize {
483        let listeners = self.stream_listeners.lock().unwrap();
484        listeners.len()
485    }
486
487    /// Receives data sent from clients through stream.
488    ///
489    /// See also: [`stream`]
490    /// ```no_run
491    /// # use http_test_server::TestServer;
492    /// # let server = TestServer::new().unwrap();
493    /// let resource = server.create_resource("/stream");
494    /// let receiver = resource.stream().stream_receiver();
495    ///
496    /// let new_message = receiver.recv().unwrap();
497    ///
498    /// for message in receiver.iter() {
499    ///     println!("Client message: {}", message);
500    /// }
501    /// ```
502    /// [`stream`]: struct.Resource.html#method.stream
503    pub fn stream_receiver(&self) -> mpsc::Receiver<String> {
504        let (tx, rx) = mpsc::channel();
505
506        if let Ok(mut listeners) = self.stream_listeners.lock() {
507            listeners.push(tx);
508        }
509        rx
510    }
511
512    /// Number of requests received
513    /// ```
514    /// # use http_test_server::TestServer;
515    /// # let server = TestServer::new().unwrap();
516    /// # let resource = server.create_resource("/stream");
517    /// assert_eq!(resource.request_count(), 0);
518    /// ```
519    pub fn request_count(&self) -> u32 {
520        *(self.request_count.lock().unwrap())
521    }
522
523    pub(crate) fn matches_uri(&self, uri: &str) -> bool {
524        self.uri_regex.is_match(uri) && self.matches_query_parameters(uri)
525    }
526
527    fn matches_query_parameters(&self, uri: &str) -> bool {
528        let query_params = extract_query_params(uri);
529
530        for (expected_key, expected_value) in &self.params.lock().unwrap().query {
531            if let Some(value) = query_params.get(expected_key) {
532                if expected_value != value && expected_value != "*" {
533                    return false;
534                }
535            } else {
536                return false;
537            }
538        }
539
540        true
541    }
542}
543
544impl Clone for Resource {
545    /// Returns a `Resource` copy that shares state with other copies.
546    ///
547    /// This is useful when working with same Resource across threads.
548    fn clone(&self) -> Self {
549        Resource {
550            uri: self.uri.clone(),
551            uri_regex: self.uri_regex.clone(),
552            params: self.params.clone(),
553            status_code: self.status_code.clone(),
554            custom_status_code: self.custom_status_code.clone(),
555            headers: self.headers.clone(),
556            body: self.body.clone(),
557            body_builder: self.body_builder.clone(),
558            method: self.method.clone(),
559            delay: self.delay.clone(),
560            request_count: self.request_count.clone(),
561            is_stream: self.is_stream.clone(),
562            stream_listeners: self.stream_listeners.clone()
563        }
564    }
565}
566
567pub struct RequestParameters {
568    pub path: HashMap<String, String>,
569    pub query: HashMap<String, String>
570}
571
572
573fn create_uri_regex(uri: &str) -> (Regex, URIParameters) {
574    let re = Regex::new(r"\{(?P<p>([A-z|0-9|_])+)\}").unwrap();
575    let query_regex = Regex::new(r"\?.*").unwrap();
576
577    let params: Vec<String> = re.captures_iter(uri).filter_map(|cap| {
578        match cap.name("p") {
579            Some(p) => Some(String::from(p.as_str())),
580            None => None
581        }
582    }).collect();
583
584    let query_params = extract_query_params(uri);
585
586    let pattern = query_regex.replace(uri, "");
587    let pattern = re.replace_all(&pattern, r"(?P<$p>[^//|/?]+)");
588
589    (Regex::new(&pattern).unwrap(), URIParameters { path: params, query: query_params})
590}
591
592fn extract_query_params(uri: &str) -> HashMap<String, String> {
593    let query_regex = Regex::new(r"((?P<qk>[^&]+)=(?P<qv>[^&]+))*").unwrap();
594    let path_regex = Regex::new(r".*\?").unwrap();
595    let only_query_parameters = path_regex.replace(uri, "");
596
597    query_regex.captures_iter(&only_query_parameters).filter_map(|cap| {
598        if let Some(query_key) = cap.name("qk") {
599            let query_value = match cap.name("qv") {
600                Some(v) => String::from(v.as_str()),
601                None => String::from("")
602            };
603            return Some((String::from(query_key.as_str()), query_value));
604        }
605        None
606    }).collect()
607}
608
609#[cfg(test)]
610mod tests {
611    use super::*;
612    use std::thread;
613
614    #[test]
615    fn should_convert_to_response_string() {
616        let resource = Resource::new("/");
617        resource.status(Status::NotFound);
618
619        assert_eq!(resource.build_response("/"), "HTTP/1.1 404 Not Found\r\n\r\n");
620    }
621
622    #[test]
623    fn should_convert_to_response_with_body() {
624        let resource = Resource::new("/");
625        resource.status(Status::Accepted).body("hello!");
626
627        assert_eq!(resource.build_response("/"), "HTTP/1.1 202 Accepted\r\n\r\nhello!");
628    }
629
630    #[test]
631    fn should_allows_custom_status() {
632        let resource = Resource::new("/");
633        resource.custom_status(666, "The Number Of The Beast").body("hello!");
634
635        assert_eq!(resource.build_response("/"), "HTTP/1.1 666 The Number Of The Beast\r\n\r\nhello!");
636    }
637
638    #[test]
639    fn should_overwrite_custom_status_with_status() {
640        let resource = Resource::new("/");
641        resource.custom_status(666, "The Number Of The Beast").status(Status::Forbidden).body("hello!");
642
643        assert_eq!(resource.build_response("/"), "HTTP/1.1 403 Forbidden\r\n\r\nhello!");
644    }
645
646    #[test]
647    fn should_add_headers() {
648        let resource = Resource::new("/");
649        resource
650            .header("Content-Type", "application/json")
651            .body("hello!");
652
653        assert_eq!(resource.build_response("/"), "HTTP/1.1 200 Ok\r\nContent-Type: application/json\r\n\r\nhello!");
654    }
655
656    #[test]
657    fn should_append_headers() {
658        let resource = Resource::new("/");
659        resource
660            .header("Content-Type", "application/json")
661            .header("Connection", "Keep-Alive")
662            .body("hello!");
663
664        let response = resource.build_response("/");
665
666        assert!(response.contains("Content-Type: application/json\r\n"));
667        assert!(response.contains("Connection: Keep-Alive\r\n"));
668    }
669
670    #[test]
671    fn should_increment_request_count() {
672        let resource = Resource::new("/");
673        resource.body("hello!");
674
675        resource.increment_request_count();
676        resource.increment_request_count();
677        resource.increment_request_count();
678
679        assert_eq!(resource.request_count(), 3);
680    }
681
682    #[test]
683    fn clones_should_share_same_state() {
684        let resource = Resource::new("/");
685        let dolly = resource.clone();
686
687        resource.increment_request_count();
688        dolly.increment_request_count();
689
690        assert_eq!(resource.request_count(), dolly.request_count());
691        assert_eq!(resource.request_count(), 2);
692    }
693
694    #[test]
695    fn should_set_as_stream() {
696        let resource = Resource::new("/");
697
698        resource.stream().status(Status::Accepted);
699
700        assert!(resource.is_stream());
701    }
702
703
704    #[test]
705    fn should_notify_data() {
706        let resource = Resource::new("/");
707
708        let receiver = resource.stream_receiver();
709        resource.send("some data").send("some data");
710
711        assert_eq!(receiver.recv().unwrap(), "some data");
712        assert_eq!(receiver.recv().unwrap(), "some data");
713    }
714
715    #[test]
716    fn should_close_connections() {
717        let resource = Resource::new("/");
718        let res = resource.clone();
719        let receiver = resource.stream_receiver();
720
721        thread::spawn(move || {
722            res.send("some data");
723            res.send("some data");
724            res.close_open_connections();
725        });
726
727        let mut string = String::new();
728
729        for data in receiver.iter() {
730            string = string + &data;
731        }
732
733        assert_eq!(string, "some datasome data");
734    }
735
736    #[test]
737    fn should_return_number_of_connecteds_users() {
738        let resource = Resource::new("/");
739        let _receiver = resource.stream_receiver();
740        let _receiver_2 = resource.stream_receiver();
741
742        assert_eq!(resource.open_connections_count(), 2);
743    }
744
745
746    #[test]
747    fn should_decrease_count_when_receiver_dropped() {
748        let resource = Resource::new("/");
749        resource.stream_receiver();
750
751        resource.send("some data");
752
753        assert_eq!(resource.open_connections_count(), 0);
754    }
755
756    #[test]
757    fn should_send_data_with_line_break() {
758        let resource = Resource::new("/");
759
760        let receiver = resource.stream_receiver();
761        resource.send_line("some data").send_line("again");
762
763        assert_eq!(receiver.recv().unwrap(), "some data\n");
764        assert_eq!(receiver.recv().unwrap(), "again\n");
765    }
766
767    #[test]
768    fn should_set_delay() {
769        let resource = Resource::new("/");
770        resource.delay(Duration::from_millis(200));
771
772        assert_eq!(resource.get_delay(), Some(Duration::from_millis(200)));
773    }
774
775    #[test]
776    fn should_match_uri() {
777        let resource = Resource::new("/some-endpoint");
778        assert!(resource.matches_uri("/some-endpoint"));
779    }
780
781    #[test]
782    fn should_not_match_uri_when_uri_does_not_match() {
783        let resource = Resource::new("/some-endpoint");
784        assert!(!resource.matches_uri("/some-other-endpoint"));
785    }
786
787    #[test]
788    fn should_match_uri_with_path_params() {
789        let resource = Resource::new("/endpoint/{param1}/some/{param2}");
790        assert!(resource.matches_uri("/endpoint/123/some/abc"));
791        assert!(resource.matches_uri("/endpoint/123-345/some/abc"));
792    }
793
794    #[test]
795    fn should_not_match_uri_with_path_params_when_uri_does_not_match() {
796        let resource = Resource::new("/endpoint/{param1}/some/{param2}");
797        assert!(!resource.matches_uri("/endpoint/123/some/"));
798    }
799
800    #[test]
801    fn should_match_uri_with_query_params() {
802        let resource = Resource::new("/endpoint?userId=123");
803        assert!(resource.matches_uri("/endpoint?userId=123"));
804    }
805
806    #[test]
807    fn should_not_match_uri_with_wrong_query_parameter() {
808        let resource = Resource::new("/endpoint?userId=123");
809        assert!(!resource.matches_uri("/endpoint?userId=abc"));
810    }
811
812    #[test]
813    fn should_match_uri_with_multiple_query_params() {
814        let resource = Resource::new("/endpoint?userId=123&hello=abc");
815        assert!(resource.matches_uri("/endpoint?userId=123&hello=abc"));
816    }
817
818    #[test]
819    fn should_match_uri_with_wildcard_query_params() {
820        let resource = Resource::new("/endpoint?userId=123&collectionId=*");
821        assert!(resource.matches_uri("/endpoint?userId=123&collectionId=banana"));
822    }
823
824    #[test]
825    fn should_match_uri_with_query_params_in_different_order() {
826        let resource = Resource::new("/endpoint?hello=abc&userId=123");
827        assert!(resource.matches_uri("/endpoint?userId=123&hello=abc"));
828    }
829
830    #[test]
831    fn should_not_match_uri_when_one_query_param_is_wrong() {
832        let resource = Resource::new("/endpoint?userId=123&hello=abc");
833        assert!(!resource.matches_uri("/endpoint?userId=123&hello=bbc"));
834    }
835
836    #[test]
837    fn should_match_uri_with_query_params_defined_through_method() {
838        let resource = Resource::new("/endpoint");
839        resource.query("hello", "abc").query("userId", "123");
840        assert!(resource.matches_uri("/endpoint?userId=123&hello=abc"));
841    }
842
843    #[test]
844    fn should_match_uri_with_wildcard_query_params_defined_through_method() {
845        let resource = Resource::new("/endpoint");
846        resource.query("hello", "*");
847        assert!(resource.matches_uri("/endpoint?hello=1234"));
848    }
849
850    #[test]
851    fn should_build_response() {
852        let resource = Resource::new("/");
853        resource.status(Status::NotFound);
854
855        assert_eq!(resource.build_response("/"), "HTTP/1.1 404 Not Found\r\n\r\n");
856    }
857
858    #[test]
859    fn should_build_response_with_body() {
860        let resource = Resource::new("/");
861        resource.status(Status::Accepted).body("hello!");
862
863        assert_eq!(resource.build_response("/"), "HTTP/1.1 202 Accepted\r\n\r\nhello!");
864    }
865
866    #[test]
867    fn should_build_response_with_path_parameters() {
868        let resource = Resource::new("/endpoint/{param1}/{param2}");
869        resource.status(Status::Accepted).body("Hello: {path.param2} {path.param1}");
870
871        assert_eq!(resource.build_response("/endpoint/123/abc"), "HTTP/1.1 202 Accepted\r\n\r\nHello: abc 123");
872    }
873
874    #[test]
875    fn should_build_response_with_query_parameters() {
876        let resource = Resource::new("/endpoint/{param1}?param2=111");
877        resource.status(Status::Accepted).body("Hello: {query.param2} {path.param1}");
878
879        assert_eq!(resource.build_response("/endpoint/123?param2=111"), "HTTP/1.1 202 Accepted\r\n\r\nHello: 111 123");
880    }
881
882    #[test]
883    fn should_build_response_with_wildcard_query_parameters() {
884        let resource = Resource::new("/endpoint/{param1}?param2=111&param3=*");
885        resource.status(Status::Accepted).body("Hello: {query.param3}");
886
887        assert_eq!(resource.build_response("/endpoint/123?param2=111&param3=banana"), "HTTP/1.1 202 Accepted\r\n\r\nHello: banana");
888    }
889
890    #[test]
891    fn should_build_response_using_body_fn() {
892        let resource = Resource::new("/endpoint/{param1}/{param2}");
893        resource.status(Status::Accepted).body_fn(|params| {
894            format!("Hello: {} {}", params.path.get("param2").unwrap(), params.path.get("param1").unwrap())
895        });
896
897        assert_eq!(resource.build_response("/endpoint/123/abc"), "HTTP/1.1 202 Accepted\r\n\r\nHello: abc 123");
898    }
899
900    #[test]
901    #[should_panic(expected = "You can't define 'body_fn' when 'body' is already defined")]
902    fn should_fail_when_trying_to_define_body_fn_after_defining_body() {
903        let resource = Resource::new("/endpoint/{param1}/{param2}");
904        resource.body("some body");
905        resource.body_fn(|_params| String::from(""));
906    }
907
908    #[test]
909    #[should_panic(expected = "You can't define 'body' when 'body_fn' is already defined")]
910    fn should_fail_when_trying_to_define_body_after_defining_body_fn() {
911        let resource = Resource::new("/endpoint/{param1}/{param2}");
912        resource.body_fn(|_params| String::from(""));
913        resource.body("some body");
914    }
915}