tests/app_send_tests.rs 5.1 K raw
1
//! End-to-end: TUI action → async send task → response/token state update.
2
3
use std::path::PathBuf;
4
5
use cielago::app::App;
6
use cielago::model::{Collection, KeyValueRow, Method, OAuthConfig, SavedRequest};
7
use cielago::store::AppConfig;
8
use wiremock::matchers::{method, path};
9
use wiremock::{Mock, MockServer, ResponseTemplate};
10
11
fn app_with(base_url: String) -> App {
12
    let mut c = Collection::new("test");
13
    c.servers = vec![base_url];
14
    let mut req = SavedRequest::blank("get thing");
15
    req.method = Method::Get;
16
    req.path = "/things/1".into();
17
    req.headers
18
        .push(KeyValueRow::new("X-Request-Id", "{{uuid}}", true));
19
    c.requests = vec![req];
20
    App::new(
21
        c,
22
        PathBuf::from("/tmp/cielago-test.json"),
23
        AppConfig::default(),
24
    )
25
}
26
27
#[tokio::test]
28
async fn send_from_app_updates_response_pane() {
29
    let server = MockServer::start().await;
30
    Mock::given(method("GET"))
31
        .and(path("/things/1"))
32
        .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({"id": 1})))
33
        .mount(&server)
34
        .await;
35
36
    let mut app = app_with(server.uri());
37
    assert!(app.response.is_none());
38
39
    app.send_selected();
40
    assert!(app.sending);
41
42
    let outcome = app.rx.recv().await.expect("send outcome");
43
    app.handle_outcome(outcome);
44
45
    assert!(!app.sending);
46
    let resp = app.response.as_ref().expect("response recorded");
47
    assert_eq!(resp.status, 200);
48
    assert!(resp.body.contains("\"id\": 1"));
49
    assert!(app.status.contains("200"));
50
51
    // {{uuid}} was substituted in the outgoing header.
52
    let received = server.received_requests().await.unwrap();
53
    let id = received[0]
54
        .headers
55
        .get("x-request-id")
56
        .unwrap()
57
        .to_str()
58
        .unwrap();
59
    assert!(uuid::Uuid::parse_str(id).is_ok(), "got {id}");
60
}
61
62
#[test]
63
fn url_input_leading_verb_sets_method() {
64
    let mut app = app_with("http://example.com".into());
65
    app.select_request(0);
66
67
    // A leading verb sets the method and is stripped from the path.
68
    app.apply_url_input("post /pets");
69
    assert_eq!(app.collection.requests[0].method, Method::Post);
70
    assert_eq!(app.collection.requests[0].path, "/pets");
71
72
    // A bare path leaves the method untouched.
73
    app.apply_url_input("/pets/1");
74
    assert_eq!(app.collection.requests[0].method, Method::Post);
75
    assert_eq!(app.collection.requests[0].path, "/pets/1");
76
77
    // A lone verb with no remainder is a path, not a method.
78
    app.apply_url_input("delete");
79
    assert_eq!(app.collection.requests[0].method, Method::Post);
80
    assert_eq!(app.collection.requests[0].path, "/delete");
81
}
82
83
#[tokio::test]
84
async fn send_with_oauth_fetches_and_caches_token() {
85
    let server = MockServer::start().await;
86
    Mock::given(method("POST"))
87
        .and(path("/token"))
88
        .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
89
            "access_token": "cached-tok",
90
            "expires_in": 3600
91
        })))
92
        .expect(1) // fetched once, then cached
93
        .mount(&server)
94
        .await;
95
    Mock::given(method("GET"))
96
        .and(path("/things/1"))
97
        .respond_with(ResponseTemplate::new(200))
98
        .expect(2)
99
        .mount(&server)
100
        .await;
101
102
    let mut app = app_with(server.uri());
103
    app.collection.auth = Some(OAuthConfig {
104
        token_url: format!("{}/token", server.uri()),
105
        client_id: "id".into(),
106
        client_secret: "secret".into(),
107
        scopes: vec![],
108
        auth_style: cielago::model::AuthStyle::Basic,
109
        ..Default::default()
110
    });
111
112
    // First send: fetches a token.
113
    app.send_selected();
114
    let outcome = app.rx.recv().await.unwrap();
115
    app.handle_outcome(outcome);
116
    assert_eq!(app.response.as_ref().unwrap().status, 200);
117
    assert!(app.token.is_some());
118
119
    // Second send: reuses the cached token (token endpoint expect(1)).
120
    app.send_selected();
121
    let outcome = app.rx.recv().await.unwrap();
122
    app.handle_outcome(outcome);
123
    assert_eq!(app.response.as_ref().unwrap().status, 200);
124
125
    // Both API calls carried the bearer token.
126
    let received = server.received_requests().await.unwrap();
127
    let api_calls: Vec<_> = received
128
        .iter()
129
        .filter(|r| r.url.path() == "/things/1")
130
        .collect();
131
    assert_eq!(api_calls.len(), 2);
132
    for r in api_calls {
133
        assert_eq!(
134
            r.headers.get("authorization").unwrap().to_str().unwrap(),
135
            "Bearer cached-tok"
136
        );
137
    }
138
}
139
140
#[tokio::test]
141
async fn send_without_server_shows_status_error() {
142
    let mut app = app_with("".into());
143
    app.collection.servers.clear();
144
    app.send_selected();
145
    assert!(!app.sending);
146
    assert!(app.status.contains("No server configured"));
147
}
148
149
// After naming a new request, the flow chains into the URL edit prefilled
150
// with the default method so `GET ` is shown awaiting a route.
151
#[test]
152
fn new_request_chains_to_url_prefilled_with_method() {
153
    let mut app = app_with("http://example.com".into());
154
    app.start_edit(cielago::app::EditTarget::NewRequest);
155
    app.input.set("make thing");
156
    app.commit_edit();
157
    assert_eq!(app.editing, Some(cielago::app::EditTarget::Url));
158
    assert_eq!(app.input.buf, "GET ");
159
}