1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
#![cfg_attr(test, allow(clippy::expect_used, clippy::unwrap_used))]
use std::path::Path;
use std::process::Child;
use std::process::Command;
use std::time::Duration;
use std::time::Instant;
use serde_json::Value;
use serde_json::json;
/// A running fixture that dies with the test.
struct Fixture {
child: Child,
url: String,
data: Value,
agent: ureq::Agent,
_dir: tempfile::TempDir,
}
impl Drop for Fixture {
fn drop(&mut self) {
let _ = self.child.kill();
let _ = self.child.wait();
}
}
impl Fixture {
fn start() -> Self {
let dir = tempfile::tempdir().expect("tempdir");
let handshake = dir.path().join("handshake.json");
let child = Command::new(env!("CARGO_BIN_EXE_parallax-fixture"))
.env("PARALLAX_HANDSHAKE", &handshake)
.spawn()
.expect("spawn fixture");
let body = wait_for(&handshake);
let url = body["url"].as_str().expect("handshake url").to_owned();
let agent = ureq::Agent::config_builder()
.http_status_as_error(false)
.build()
.into();
Fixture {
child,
url,
data: body["data"].clone(),
agent,
_dir: dir,
}
}
fn get(&self, path: &str) -> (u16, String) {
let mut response = self
.agent
.get(format!("{}{path}", self.url))
.call()
.expect("get");
let body = response.body_mut().read_to_string().expect("body");
(response.status().as_u16(), body)
}
fn post_json(&self, path: &str, body: Value) -> (u16, Value) {
let mut response = self
.agent
.post(format!("{}{path}", self.url))
.send_json(body)
.expect("post");
let body = response.body_mut().read_json().expect("json body");
(response.status().as_u16(), body)
}
}
fn wait_for(handshake: &Path) -> Value {
let deadline = Instant::now() + Duration::from_secs(10);
loop {
if let Ok(text) = fs_err::read_to_string(handshake) {
return serde_json::from_str(&text).expect("handshake is complete JSON");
}
assert!(Instant::now() < deadline, "no handshake after 10s");
std::thread::sleep(Duration::from_millis(20));
}
}
#[test]
fn handshake_serves_the_site() {
let app = Fixture::start();
assert_eq!(app.data, json!({"featured_item": 2}));
let (status, body) = app.get("/");
assert_eq!(status, 200);
assert!(body.contains(r#"class="dashboard""#));
assert!(body.contains("Water the ferns"));
let (status, body) = app.get("/items/2.html");
assert_eq!(status, 200);
assert!(body.contains(r#"class="item-detail""#));
}
#[test]
fn fixture_route_round_trips() {
let app = Fixture::start();
let (status, item) = app.post_json(
"/__fixtures/items",
json!({"title": "Added by the flow", "tag": "parallax"}),
);
assert_eq!(status, 201);
assert_eq!(
item,
json!({"id": 6, "title": "Added by the flow", "tag": "parallax", "notes": "", "done": false}),
);
let (status, body) = app.post_json("/__fixtures/items", json!({"title": " "}));
assert_eq!(status, 422);
assert_eq!(body["error"], "title must not be empty");
}