tests/import_tests.rs 9.9 K raw
1
use cielago::model::{Method, variables_map};
2
use cielago::openapi::{import_spec, load_spec};
3
use cielago::store;
4
5
fn fixture_path(name: &str) -> String {
6
    format!("{}/tests/fixtures/{name}", env!("CARGO_MANIFEST_DIR"))
7
}
8
9
async fn import_fixture(name: &str, coll_name: &str) -> cielago::model::Collection {
10
    let doc = load_spec(&fixture_path(name)).await.unwrap();
11
    import_spec(&doc, coll_name, Some(fixture_path(name)))
12
}
13
14
#[tokio::test]
15
async fn imports_petstore_30() {
16
    let c = import_fixture("petstore30.yaml", "pets").await;
17
18
    assert_eq!(
19
        c.servers,
20
        vec![
21
            "https://api.pets.example.com/v1".to_string(),
22
            "https://staging.pets.example.com/v1".to_string()
23
        ]
24
    );
25
26
    // OAuth clientCredentials flow is detected and prefilled.
27
    let auth = c.auth.as_ref().expect("auth should be prefilled");
28
    assert_eq!(auth.token_url, "https://auth.pets.example.com/oauth/token");
29
    assert_eq!(auth.scopes, vec!["read:pets", "write:pets"]);
30
    assert!(auth.client_id.is_empty());
31
32
    assert_eq!(c.requests.len(), 4);
33
34
    let list = c.requests.iter().find(|r| r.name == "listPets").unwrap();
35
    assert_eq!(list.method, Method::Get);
36
    assert_eq!(list.path, "/pets");
37
    assert_eq!(list.tags, vec!["pets"]);
38
    // Optional query params are populated but disabled; defaults prefilled.
39
    let limit = list.query.iter().find(|q| q.key == "limit").unwrap();
40
    assert!(!limit.enabled);
41
    assert_eq!(limit.value, "20");
42
    let filter = list.query.iter().find(|q| q.key == "filter").unwrap();
43
    assert!(!filter.enabled);
44
    assert_eq!(filter.value, "");
45
    // Required header param is enabled with its example.
46
    let tenant = list
47
        .headers
48
        .iter()
49
        .find(|h| h.key == "X-Tenant-Id")
50
        .unwrap();
51
    assert!(tenant.enabled);
52
    assert_eq!(tenant.value, "acme");
53
54
    // Authored media-type example wins for the body.
55
    let create = c.requests.iter().find(|r| r.name == "createPet").unwrap();
56
    assert_eq!(create.method, Method::Post);
57
    let body = create.body.as_deref().unwrap();
58
    assert!(body.contains("\"name\": \"Fido\""), "body was: {body}");
59
60
    // $ref'd path parameter is resolved and its example prefilled.
61
    let get_pet = c.requests.iter().find(|r| r.name == "getPet").unwrap();
62
    assert_eq!(get_pet.path, "/pets/{petId}");
63
    let pet_id = get_pet
64
        .path_params
65
        .iter()
66
        .find(|p| p.key == "petId")
67
        .unwrap();
68
    assert!(pet_id.enabled);
69
    assert_eq!(pet_id.value, "123");
70
71
    // Summary used as name when operationId is absent; body generated from
72
    // schema, uuid format becomes the {{uuid}} variable.
73
    let order = c.requests.iter().find(|r| r.name == "Place order").unwrap();
74
    assert_eq!(order.tags, vec!["store"]);
75
    let body = order.body.as_deref().unwrap();
76
    assert!(body.contains("\"petId\": 1"), "body was: {body}");
77
    assert!(
78
        body.contains("\"requestId\": \"{{uuid}}\""),
79
        "body was: {body}"
80
    );
81
}
82
83
#[tokio::test]
84
async fn import_captures_docs_for_the_docs_tab() {
85
    let c = import_fixture("petstore30.yaml", "pets docs").await;
86
87
    let list = c.requests.iter().find(|r| r.name == "listPets").unwrap();
88
    assert_eq!(
89
        list.description.as_deref(),
90
        Some("Lists pets, newest first.")
91
    );
92
93
    let limit = list.docs.iter().find(|d| d.name == "limit").unwrap();
94
    assert_eq!(limit.location, "query");
95
    assert_eq!(limit.ty, "integer");
96
    assert!(!limit.required);
97
    assert_eq!(limit.default.as_deref(), Some("20"));
98
    assert_eq!(
99
        limit.description.as_deref(),
100
        Some("How many pets to return.")
101
    );
102
103
    // The options a field accepts are what the tab is for.
104
    let status = list.docs.iter().find(|d| d.name == "status").unwrap();
105
    assert_eq!(status.options, ["available", "pending", "sold"]);
106
107
    let tenant = list.docs.iter().find(|d| d.name == "X-Tenant-Id").unwrap();
108
    assert_eq!(tenant.location, "header");
109
    assert!(tenant.required);
110
111
    // $ref'd path parameter, documented through the reference.
112
    let get_pet = c.requests.iter().find(|r| r.name == "getPet").unwrap();
113
    let pet_id = get_pet.docs.iter().find(|d| d.name == "petId").unwrap();
114
    assert_eq!(pet_id.location, "path");
115
    assert_eq!(pet_id.ty, "integer(int64)");
116
    assert!(pet_id.required);
117
118
    // Body fields come from the request body schema, `required` included.
119
    let create = c.requests.iter().find(|r| r.name == "createPet").unwrap();
120
    let body: Vec<(&str, &str, bool)> = create
121
        .docs
122
        .iter()
123
        .filter(|d| d.location == "body")
124
        .map(|d| (d.name.as_str(), d.ty.as_str(), d.required))
125
        .collect();
126
    assert_eq!(
127
        body,
128
        [
129
            ("id", "integer(int64)", false),
130
            ("name", "string", true),
131
            ("tag", "string", false)
132
        ]
133
    );
134
    assert_eq!(
135
        create
136
            .docs
137
            .iter()
138
            .find(|d| d.name == "tag")
139
            .unwrap()
140
            .default
141
            .as_deref(),
142
        Some("friendly")
143
    );
144
145
    // Hand-made requests simply have none.
146
    assert!(cielago::model::SavedRequest::blank("x").docs.is_empty());
147
}
148
149
#[tokio::test]
150
async fn imports_31_json() {
151
    let c = import_fixture("api31.json", "things").await;
152
    assert_eq!(c.servers, vec!["https://things.example.com".to_string()]);
153
    assert_eq!(c.requests.len(), 1);
154
    let make = &c.requests[0];
155
    assert_eq!(make.name, "makeThing");
156
    let body = make.body.as_deref().unwrap();
157
    assert!(body.contains("\"label\": \"widget\""), "body was: {body}");
158
    assert!(body.contains("\"count\": 1"), "body was: {body}");
159
}
160
161
#[test]
162
fn summary_wins_over_operation_id_for_naming() {
163
    let doc = serde_json::json!({
164
        "paths": {
165
            "/v1/customers/{id}": {
166
                "get": {
167
                    "operationId": "CustomerControllerV1_retrieveCustomerById",
168
                    "summary": "Get customer",
169
                    "tags": ["customers"]
170
                }
171
            },
172
            "/v1/health": { "get": { "operationId": "healthCheck" } },
173
            "/v1/ping": { "get": {} }
174
        }
175
    });
176
    let c = import_spec(&doc, "svc", None);
177
178
    let cust = c
179
        .requests
180
        .iter()
181
        .find(|r| r.path.contains("customers"))
182
        .unwrap();
183
    assert_eq!(cust.name, "Get customer");
184
    assert_eq!(cust.summary.as_deref(), Some("Get customer"));
185
    assert_eq!(
186
        cust.operation_id.as_deref(),
187
        Some("CustomerControllerV1_retrieveCustomerById")
188
    );
189
190
    // operationId is the fallback when there's no summary.
191
    let health = c.requests.iter().find(|r| r.path == "/v1/health").unwrap();
192
    assert_eq!(health.name, "healthCheck");
193
    assert_eq!(health.summary, None);
194
195
    // Neither present: METHOD + path.
196
    let ping = c.requests.iter().find(|r| r.path == "/v1/ping").unwrap();
197
    assert_eq!(ping.name, "GET /v1/ping");
198
}
199
200
#[test]
201
fn label_mode_selects_the_displayed_text() {
202
    use cielago::model::LabelMode;
203
204
    let doc = serde_json::json!({
205
        "paths": {
206
            "/v1/customers/{id}": {
207
                "get": { "operationId": "CustomerControllerV1_get", "summary": "Get customer" }
208
            }
209
        }
210
    });
211
    let c = import_spec(&doc, "svc", None);
212
    let r = &c.requests[0];
213
    assert_eq!(r.label(LabelMode::Name), "Get customer");
214
    assert_eq!(r.label(LabelMode::Summary), "Get customer");
215
    assert_eq!(r.label(LabelMode::Path), "/v1/customers/{id}");
216
217
    // No summary: Summary mode falls back to the name rather than blanking.
218
    let mut bare = cielago::model::SavedRequest::blank("hand made");
219
    bare.path = "/thing".into();
220
    assert_eq!(bare.label(LabelMode::Summary), "hand made");
221
    assert_eq!(bare.label(LabelMode::Path), "/thing");
222
}
223
224
#[tokio::test]
225
async fn collection_survives_save_load_roundtrip() {
226
    let c = import_fixture("petstore30.yaml", "pets roundtrip").await;
227
    let dir = tempfile::tempdir().unwrap();
228
    let path = dir.path().join("coll.json");
229
    std::fs::write(&path, serde_json::to_string_pretty(&c).unwrap()).unwrap();
230
    let back = store::load_collection_path(&path.to_path_buf()).unwrap();
231
    assert_eq!(back.requests.len(), 4);
232
    assert_eq!(
233
        back.auth.unwrap().token_url,
234
        "https://auth.pets.example.com/oauth/token"
235
    );
236
}
237
238
#[tokio::test]
239
async fn update_replaces_routes_but_keeps_everything_else() {
240
    let mut c = import_fixture("petstore30.yaml", "pets").await;
241
    assert_eq!(c.requests.len(), 4);
242
243
    // User customisations that an update must preserve.
244
    c.variables
245
        .push(cielago::model::KeyValueRow::new("tenant", "acme", true));
246
    let auth = c.auth.as_mut().unwrap();
247
    auth.client_id = "my-id".into();
248
    auth.client_secret = "my-secret".into();
249
    c.active_server = 1;
250
    c.last_request = Some(c.requests[0].id);
251
    let servers = c.servers.clone();
252
253
    // Routes come from a different spec.
254
    let imported = import_fixture("api31.json", "pets").await;
255
    c.replace_requests_from(imported);
256
257
    // Requests were replaced wholesale by the new spec's...
258
    assert_eq!(c.requests.len(), 1);
259
    assert_eq!(c.requests[0].path, "/things");
260
    // ...and the now-stale selection pointer was dropped.
261
    assert!(c.last_request.is_none());
262
263
    // Everything else is exactly as the user left it.
264
    assert_eq!(c.servers, servers);
265
    assert_eq!(c.active_server, 1);
266
    let tenant = c.variables.iter().find(|v| v.key == "tenant").unwrap();
267
    assert_eq!(tenant.value, "acme");
268
    let auth = c.auth.as_ref().unwrap();
269
    assert_eq!(auth.token_url, "https://auth.pets.example.com/oauth/token");
270
    assert_eq!(auth.client_id, "my-id");
271
    assert_eq!(auth.client_secret, "my-secret");
272
}
273
274
#[test]
275
fn variables_map_respects_enabled() {
276
    let vars = vec![
277
        cielago::model::KeyValueRow::new("a", "1", true),
278
        cielago::model::KeyValueRow::new("b", "2", false),
279
        cielago::model::KeyValueRow::new("", "3", true),
280
    ];
281
    let map = variables_map(&vars);
282
    assert_eq!(map.get("a").unwrap(), "1");
283
    assert!(!map.contains_key("b"));
284
    assert!(!map.contains_key(""));
285
}