• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

geo-engine / geoengine / 4741906491

pending completion
4741906491

push

github

GitHub
Merge #751

303 of 303 new or added lines in 18 files covered. (100.0%)

94583 of 105909 relevant lines covered (89.31%)

74046.05 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

89.27
/services/src/api/mod.rs
1
use crate::contexts::SessionId;
2
use crate::handlers::ErrorResponse;
3
use actix_web::dev::ServiceResponse;
4
use actix_web::http::{header, Method};
5
use actix_web::test::TestRequest;
6
use actix_web_httpauth::headers::authorization::Bearer;
7
use std::collections::HashMap;
8
use std::future::Future;
9
use utoipa::openapi::path::{Parameter, ParameterIn};
10
use utoipa::openapi::schema::AdditionalProperties;
11
use utoipa::openapi::{
12
    Components, KnownFormat, OpenApi, PathItemType, Ref, RefOr, Schema, SchemaFormat, SchemaType,
13
};
14
use uuid::Uuid;
15

16
pub mod model;
17

18
fn throw_if_invalid_ref(reference: &Ref, components: &Components) {
178✔
19
    const SCHEMA_REF_PREFIX: &str = "#/components/schemas/";
178✔
20
    const RESPONSE_REF_PREFIX: &str = "#/components/responses/";
178✔
21

178✔
22
    if reference.ref_location.starts_with(SCHEMA_REF_PREFIX) {
178✔
23
        let schema_name = &reference.ref_location[SCHEMA_REF_PREFIX.len()..];
158✔
24
        assert!(
158✔
25
            components.schemas.contains_key(schema_name),
158✔
26
            "Referenced the unknown schema `{schema_name}`"
8✔
27
        );
28
    } else if reference.ref_location.starts_with(RESPONSE_REF_PREFIX) {
20✔
29
        let response_name = &reference.ref_location[RESPONSE_REF_PREFIX.len()..];
20✔
30
        assert!(
20✔
31
            components.responses.contains_key(response_name),
20✔
32
            "Referenced the unknown response `{response_name}`"
×
33
        );
34
    } else {
35
        panic!("Invalid reference type");
×
36
    }
37
}
170✔
38

39
/// Recursively checks that schemas referenced in the given schema object exist in the provided map.
40
fn can_resolve_schema(schema: RefOr<Schema>, components: &Components) {
224✔
41
    match schema {
224✔
42
        RefOr::Ref(reference) => {
156✔
43
            throw_if_invalid_ref(&reference, components);
156✔
44
        }
156✔
45
        RefOr::T(concrete) => match concrete {
68✔
46
            Schema::Array(arr) => {
8✔
47
                can_resolve_schema(*arr.items, components);
8✔
48
            }
8✔
49
            Schema::Object(obj) => {
45✔
50
                for property in obj.properties.into_values() {
45✔
51
                    can_resolve_schema(property, components);
2✔
52
                }
2✔
53
                if let Some(additional_properties) = obj.additional_properties {
44✔
54
                    if let AdditionalProperties::RefOr(properties_schema) = *additional_properties {
×
55
                        can_resolve_schema(properties_schema, components);
×
56
                    }
×
57
                }
44✔
58
            }
59
            Schema::OneOf(oo) => {
1✔
60
                for item in oo.items {
2✔
61
                    can_resolve_schema(item, components);
1✔
62
                }
1✔
63
            }
64
            Schema::AllOf(ao) => {
14✔
65
                for item in ao.items {
28✔
66
                    can_resolve_schema(item, components);
14✔
67
                }
14✔
68
            }
69
            _ => panic!("Unknown schema type"),
×
70
        },
71
    }
72
}
221✔
73

74
/// Loops through all registered HTTP handlers and ensures that the referenced schemas
75
/// (inside of request bodies, parameters or responses) exist and can be resolved.
76
pub fn can_resolve_api(api: OpenApi) {
5✔
77
    let components = api.components.expect("api has at least one component");
5✔
78

79
    for path_item in api.paths.paths.into_values() {
57✔
80
        for operation in path_item.operations.into_values() {
65✔
81
            if let Some(request_body) = operation.request_body {
65✔
82
                for content in request_body.content.into_values() {
17✔
83
                    can_resolve_schema(content.schema, &components);
17✔
84
                }
17✔
85
            }
48✔
86

87
            if let Some(parameters) = operation.parameters {
64✔
88
                for parameter in parameters {
182✔
89
                    if let Some(schema) = parameter.schema {
137✔
90
                        can_resolve_schema(schema, &components);
136✔
91
                    }
136✔
92
                }
93
            }
18✔
94

95
            for response in operation.responses.responses.into_values() {
81✔
96
                match response {
81✔
97
                    RefOr::Ref(reference) => {
20✔
98
                        throw_if_invalid_ref(&reference, &components);
20✔
99
                    }
20✔
100
                    RefOr::T(concrete) => {
61✔
101
                        for content in concrete.content.into_values() {
61✔
102
                            can_resolve_schema(content.schema, &components);
42✔
103
                        }
42✔
104
                    }
105
                }
106
            }
107
        }
108
    }
109
}
2✔
110

111
struct RunnableExample<'a, C, F, Fut>
112
where
113
    F: Fn(TestRequest, C) -> Fut,
114
    Fut: Future<Output = ServiceResponse>,
115
{
116
    components: &'a Components,
117
    http_method: &'a PathItemType,
118
    uri: &'a str,
119
    parameters: &'a Option<Vec<Parameter>>,
120
    body: serde_json::Value,
121
    with_auth: bool,
122
    ctx: C,
123
    session_id: SessionId,
124
    send_test_request: &'a F,
125
}
126

127
impl<'a, C, F, Fut> RunnableExample<'a, C, F, Fut>
128
where
129
    F: Fn(TestRequest, C) -> Fut,
130
    Fut: Future<Output = ServiceResponse>,
131
{
132
    fn get_actix_http_method(&self) -> Method {
8✔
133
        match self.http_method {
8✔
134
            PathItemType::Get => Method::GET,
×
135
            PathItemType::Post => Method::POST,
6✔
136
            PathItemType::Put => Method::PUT,
1✔
137
            PathItemType::Delete => Method::DELETE,
1✔
138
            PathItemType::Options => Method::OPTIONS,
×
139
            PathItemType::Head => Method::HEAD,
×
140
            PathItemType::Patch => Method::PATCH,
×
141
            PathItemType::Trace => Method::TRACE,
×
142
            PathItemType::Connect => Method::CONNECT,
×
143
        }
144
    }
8✔
145

146
    #[allow(clippy::unimplemented)]
147
    fn get_default_parameter_value(schema: &Schema) -> String {
4✔
148
        match schema {
4✔
149
            Schema::Object(obj) => match obj.schema_type {
4✔
150
                SchemaType::String => match &obj.format {
×
151
                    Some(SchemaFormat::KnownFormat(format)) => match format {
×
152
                        KnownFormat::Uuid => Uuid::new_v4().to_string(),
×
153
                        _ => unimplemented!(),
×
154
                    },
155
                    None => "asdf".to_string(),
2✔
156
                    _ => unimplemented!(),
×
157
                },
158
                SchemaType::Integer | SchemaType::Number => "42".to_string(),
2✔
159
                SchemaType::Boolean => "false".to_string(),
×
160
                _ => unimplemented!(),
×
161
            },
162
            _ => unimplemented!(),
×
163
        }
164
    }
4✔
165

166
    fn resolve_schema(&'a self, ref_or: &'a RefOr<Schema>) -> &Schema {
4✔
167
        match ref_or {
4✔
168
            RefOr::Ref(reference) => {
×
169
                const SCHEMA_REF_PREFIX_LEN: usize = "#/components/schemas/".len();
×
170
                throw_if_invalid_ref(reference, self.components);
×
171
                let schema_name = &reference.ref_location[SCHEMA_REF_PREFIX_LEN..];
×
172
                self.resolve_schema(
×
173
                    self.components
×
174
                        .schemas
×
175
                        .get(schema_name)
×
176
                        .expect("checked before"),
×
177
                )
×
178
            }
179
            RefOr::T(concrete) => concrete,
4✔
180
        }
181
    }
4✔
182

183
    fn insert_parameters(&self, parameters: &Vec<Parameter>) -> TestRequest {
2✔
184
        let mut req = TestRequest::default();
2✔
185
        let mut uri = self.uri.to_string();
2✔
186
        let mut query_params = HashMap::new();
2✔
187
        let mut cookies = HashMap::new();
2✔
188

189
        for parameter in parameters {
6✔
190
            let schema = self.resolve_schema(
4✔
191
                parameter
4✔
192
                    .schema
4✔
193
                    .as_ref()
4✔
194
                    .expect("utoipa adds schema everytime"),
4✔
195
            );
4✔
196
            let value = Self::get_default_parameter_value(schema);
4✔
197

4✔
198
            match parameter.parameter_in {
4✔
199
                ParameterIn::Query => {
2✔
200
                    query_params.insert(parameter.name.as_str(), value);
2✔
201
                }
2✔
202
                ParameterIn::Path => {
2✔
203
                    uri = uri.replace(&format!("{{{}}}", parameter.name), value.as_str());
2✔
204
                }
2✔
205
                ParameterIn::Header => {
×
206
                    req = req.append_header((parameter.name.as_str(), value));
×
207
                }
×
208
                ParameterIn::Cookie => {
×
209
                    cookies.insert(parameter.name.as_str(), value);
×
210
                }
×
211
            }
212
        }
213
        if !cookies.is_empty() {
2✔
214
            req = req.append_header((
×
215
                header::COOKIE,
×
216
                serde_urlencoded::to_string(cookies)
×
217
                    .unwrap()
×
218
                    .replace('&', "; "),
×
219
            ));
×
220
        }
2✔
221
        if !query_params.is_empty() {
2✔
222
            uri = format!(
2✔
223
                "{}?{}",
2✔
224
                uri,
2✔
225
                serde_urlencoded::to_string(query_params).unwrap()
2✔
226
            );
2✔
227
        }
2✔
228
        req.uri(uri.as_str())
2✔
229
    }
2✔
230

231
    fn build_request(&self) -> TestRequest {
8✔
232
        let http_method = self.get_actix_http_method();
8✔
233
        let mut req;
234

235
        if let Some(parameters) = self.parameters {
8✔
236
            req = self.insert_parameters(parameters);
2✔
237
        } else {
6✔
238
            req = TestRequest::default().uri(self.uri);
6✔
239
        }
6✔
240
        req = req.method(http_method);
8✔
241

8✔
242
        if self.with_auth {
8✔
243
            req = req.append_header((
6✔
244
                header::AUTHORIZATION,
6✔
245
                Bearer::new(self.session_id.to_string()),
6✔
246
            ));
6✔
247
        }
6✔
248
        req.append_header((header::CONTENT_TYPE, "application/json"))
8✔
249
            .set_json(&self.body)
8✔
250
    }
8✔
251

252
    async fn run(self) -> ServiceResponse {
8✔
253
        let req = self.build_request();
8✔
254
        (self.send_test_request)(req, self.ctx).await
8✔
255
    }
8✔
256

257
    /// # Panics
258
    /// Will panic if an example cannot be run due to incomplete or
259
    /// outdated OpenAPI documentation.
260
    async fn check_for_bad_documentation(self) {
8✔
261
        let res = self.run().await;
8✔
262

263
        if res.status() == 400 {
8✔
264
            let method = res.request().head().method.to_string();
6✔
265
            let path = res.request().path().to_string();
6✔
266
            let body: ErrorResponse = actix_web::test::read_body_json(res).await;
6✔
267

268
            match body.error.as_str() {
6✔
269
                "NotFound" | "MethodNotAllowed" => panic!(
6✔
270
                    "The handler of the example at {method} {path} wasn't reachable. \
×
271
                    Check if the http method and path parameters are correctly set in the documentation."
×
272
                ),
×
273
                "UnableToParseQueryString" => panic!(
6✔
274
                    "The example at {method} {path} threw an UnableToParseQueryString error. \
×
275
                    Check if the query parameters are correctly set in the documentation."
×
276
                ),
×
277
                "BodyDeserializeError" => panic!(
6✔
278
                    "The example at {method} {path} threw an BodyDeserializeError. \
1✔
279
                    Check if there were schema changes and update the request body accordingly."
1✔
280
                ),
1✔
281
                _ => {}
5✔
282
            }
283
        }
2✔
284
    }
7✔
285
}
286

287
/// Runs all example requests against the provided test server to check for bad documentation,
288
/// for example due to incompatible schema changes between the time of writing the request body
289
/// and now. It can also detect if the query parameters are not documented correctly or the
290
/// request path changed.
291
#[allow(clippy::unimplemented)]
292
pub async fn can_run_examples<F1, Fut1, F2, Fut2, C>(
3✔
293
    api: OpenApi,
3✔
294
    ctx_creator: F1,
3✔
295
    send_test_request: F2,
3✔
296
) where
3✔
297
    F1: Fn() -> Fut1,
3✔
298
    Fut1: Future<Output = (C, SessionId)>,
3✔
299
    F2: Fn(TestRequest, C) -> Fut2,
3✔
300
    Fut2: Future<Output = ServiceResponse>,
3✔
301
{
3✔
302
    let components = api.components.expect("api has at least one component");
3✔
303

304
    for (uri, path_item) in api.paths.paths {
57✔
305
        for (http_method, operation) in path_item.operations {
117✔
306
            if let Some(request_body) = operation.request_body {
63✔
307
                let with_auth = operation.security.is_some();
17✔
308

309
                for content in request_body.content.into_values() {
17✔
310
                    if let Some(example) = content.example {
17✔
311
                        let (ctx, session_id) = ctx_creator().await;
4✔
312
                        RunnableExample {
4✔
313
                            components: &components,
4✔
314
                            http_method: &http_method,
4✔
315
                            uri: uri.as_str(),
4✔
316
                            parameters: &operation.parameters,
4✔
317
                            body: example,
4✔
318
                            with_auth,
4✔
319
                            ctx,
4✔
320
                            session_id,
4✔
321
                            send_test_request: &send_test_request,
4✔
322
                        }
4✔
323
                        .check_for_bad_documentation()
4✔
324
                        .await;
×
325
                    } else {
326
                        for example in content.examples.into_values() {
13✔
327
                            match example {
4✔
328
                                RefOr::Ref(_reference) => {
×
329
                                    // This never happened during testing.
×
330
                                    // It is undocumented how the references would look like.
×
331
                                    unimplemented!()
×
332
                                }
333
                                RefOr::T(concrete) => {
4✔
334
                                    if let Some(body) = concrete.value {
4✔
335
                                        let (ctx, session_id) = ctx_creator().await;
4✔
336
                                        RunnableExample {
4✔
337
                                            components: &components,
4✔
338
                                            http_method: &http_method,
4✔
339
                                            uri: uri.as_str(),
4✔
340
                                            parameters: &operation.parameters,
4✔
341
                                            body,
4✔
342
                                            with_auth,
4✔
343
                                            ctx,
4✔
344
                                            session_id,
4✔
345
                                            send_test_request: &send_test_request,
4✔
346
                                        }
4✔
347
                                        .check_for_bad_documentation()
4✔
348
                                        .await;
×
349
                                    } else {
×
350
                                        //skip external examples
×
351
                                    }
×
352
                                }
353
                            }
354
                        }
355
                    }
356
                }
357
            }
46✔
358
        }
359
    }
360
}
2✔
361

362
#[cfg(test)]
363
mod tests {
364
    use super::*;
365
    use crate::contexts::{InMemoryContext, Session, SimpleApplicationContext};
366
    use crate::datasets::upload::Volume;
367
    use crate::util::server::{configure_extractors, render_404, render_405};
368
    use actix_web::{http, middleware, post, web, App, HttpResponse, Responder};
369
    use geoengine_datatypes::util::test::TestDefault;
370
    use serde::Deserialize;
371
    use serde_json::json;
372
    use utoipa::openapi::path::{OperationBuilder, ParameterBuilder, PathItemBuilder};
373
    use utoipa::openapi::request_body::RequestBodyBuilder;
374
    use utoipa::openapi::{
375
        AllOfBuilder, ArrayBuilder, ComponentsBuilder, ContentBuilder, Object, ObjectBuilder,
376
        OneOfBuilder, OpenApiBuilder, PathItemType, PathsBuilder, ResponseBuilder,
377
    };
378
    use utoipa::ToSchema;
379

380
    #[test]
1✔
381
    #[should_panic(expected = "MissingSchema")]
382
    fn throws_because_of_invalid_ref() {
1✔
383
        throw_if_invalid_ref(&Ref::from_schema_name("MissingSchema"), &Components::new());
1✔
384
    }
1✔
385

386
    #[test]
1✔
387
    fn finds_ref() {
1✔
388
        throw_if_invalid_ref(
1✔
389
            &Ref::from_schema_name("ExistingSchema"),
1✔
390
            &ComponentsBuilder::new()
1✔
391
                .schema("ExistingSchema", RefOr::T(Schema::default()))
1✔
392
                .into(),
1✔
393
        );
1✔
394
    }
1✔
395

396
    #[test]
1✔
397
    #[should_panic(expected = "MissingSchema")]
398
    fn detects_missing_array_ref() {
1✔
399
        can_resolve_schema(
1✔
400
            RefOr::T(
1✔
401
                ArrayBuilder::new()
1✔
402
                    .items(Ref::from_schema_name("MissingSchema"))
1✔
403
                    .into(),
1✔
404
            ),
1✔
405
            &Components::new(),
1✔
406
        );
1✔
407
    }
1✔
408

409
    #[test]
1✔
410
    #[should_panic(expected = "MissingSchema")]
411
    fn detects_missing_object_ref() {
1✔
412
        can_resolve_schema(
1✔
413
            RefOr::T(
1✔
414
                ObjectBuilder::new()
1✔
415
                    .property("Prop", Ref::from_schema_name("MissingSchema"))
1✔
416
                    .into(),
1✔
417
            ),
1✔
418
            &Components::new(),
1✔
419
        );
1✔
420
    }
1✔
421

422
    #[test]
1✔
423
    #[should_panic(expected = "MissingSchema")]
424
    fn detects_missing_oneof_ref() {
1✔
425
        can_resolve_schema(
1✔
426
            RefOr::T(
1✔
427
                OneOfBuilder::new()
1✔
428
                    .item(Ref::from_schema_name("MissingSchema"))
1✔
429
                    .into(),
1✔
430
            ),
1✔
431
            &Components::new(),
1✔
432
        );
1✔
433
    }
1✔
434

435
    #[test]
1✔
436
    #[should_panic(expected = "MissingSchema")]
437
    fn detects_missing_allof_ref() {
1✔
438
        can_resolve_schema(
1✔
439
            RefOr::T(
1✔
440
                AllOfBuilder::new()
1✔
441
                    .item(Ref::from_schema_name("MissingSchema"))
1✔
442
                    .into(),
1✔
443
            ),
1✔
444
            &Components::new(),
1✔
445
        );
1✔
446
    }
1✔
447

448
    #[test]
1✔
449
    fn successfull_api_validation() {
1✔
450
        let api: OpenApi = OpenApiBuilder::new()
1✔
451
            .paths(
1✔
452
                PathsBuilder::new().path(
1✔
453
                    "asdf",
1✔
454
                    PathItemBuilder::new()
1✔
455
                        .operation(
1✔
456
                            PathItemType::Post,
1✔
457
                            OperationBuilder::new()
1✔
458
                                .parameter(
1✔
459
                                    ParameterBuilder::new()
1✔
460
                                        .schema(Some(Ref::from_schema_name("Schema1"))),
1✔
461
                                )
1✔
462
                                .request_body(Some(
1✔
463
                                    RequestBodyBuilder::new()
1✔
464
                                        .content(
1✔
465
                                            "application/json",
1✔
466
                                            ContentBuilder::new()
1✔
467
                                                .schema(Ref::from_schema_name("Schema2"))
1✔
468
                                                .into(),
1✔
469
                                        )
1✔
470
                                        .into(),
1✔
471
                                ))
1✔
472
                                .response(
1✔
473
                                    "200",
1✔
474
                                    ResponseBuilder::new().content(
1✔
475
                                        "application/json",
1✔
476
                                        ContentBuilder::new()
1✔
477
                                            .schema(Ref::from_schema_name("Schema3"))
1✔
478
                                            .into(),
1✔
479
                                    ),
1✔
480
                                ),
1✔
481
                        )
1✔
482
                        .into(),
1✔
483
                ),
1✔
484
            )
1✔
485
            .components(Some(
1✔
486
                ComponentsBuilder::new()
1✔
487
                    .schemas_from_iter([
1✔
488
                        ("Schema1", Schema::default()),
1✔
489
                        ("Schema2", Schema::default()),
1✔
490
                        ("Schema3", Schema::default()),
1✔
491
                    ])
1✔
492
                    .into(),
1✔
493
            ))
1✔
494
            .into();
1✔
495
        can_resolve_api(api);
1✔
496
    }
1✔
497

498
    #[test]
1✔
499
    #[should_panic(expected = "MissingSchema")]
500
    fn detect_unresolvable_request_body() {
1✔
501
        let api: OpenApi = OpenApiBuilder::new()
1✔
502
            .paths(
1✔
503
                PathsBuilder::new().path(
1✔
504
                    "asdf",
1✔
505
                    PathItemBuilder::new()
1✔
506
                        .operation(
1✔
507
                            PathItemType::Post,
1✔
508
                            OperationBuilder::new().request_body(Some(
1✔
509
                                RequestBodyBuilder::new()
1✔
510
                                    .content(
1✔
511
                                        "application/json",
1✔
512
                                        ContentBuilder::new()
1✔
513
                                            .schema(Ref::from_schema_name("MissingSchema"))
1✔
514
                                            .into(),
1✔
515
                                    )
1✔
516
                                    .into(),
1✔
517
                            )),
1✔
518
                        )
1✔
519
                        .into(),
1✔
520
                ),
1✔
521
            )
1✔
522
            .components(Some(
1✔
523
                ComponentsBuilder::new()
1✔
524
                    .schemas_from_iter([
1✔
525
                        ("Schema1", Schema::default()),
1✔
526
                        ("Schema2", Schema::default()),
1✔
527
                        ("Schema3", Schema::default()),
1✔
528
                    ])
1✔
529
                    .into(),
1✔
530
            ))
1✔
531
            .into();
1✔
532
        can_resolve_api(api);
1✔
533
    }
1✔
534

535
    #[test]
1✔
536
    #[should_panic(expected = "MissingSchema")]
537
    fn detect_unresolvable_parameter() {
1✔
538
        let api: OpenApi = OpenApiBuilder::new()
1✔
539
            .paths(
1✔
540
                PathsBuilder::new().path(
1✔
541
                    "asdf",
1✔
542
                    PathItemBuilder::new()
1✔
543
                        .operation(
1✔
544
                            PathItemType::Post,
1✔
545
                            OperationBuilder::new().parameter(
1✔
546
                                ParameterBuilder::new()
1✔
547
                                    .schema(Some(Ref::from_schema_name("MissingSchema"))),
1✔
548
                            ),
1✔
549
                        )
1✔
550
                        .into(),
1✔
551
                ),
1✔
552
            )
1✔
553
            .components(Some(
1✔
554
                ComponentsBuilder::new()
1✔
555
                    .schemas_from_iter([
1✔
556
                        ("Schema1", Schema::default()),
1✔
557
                        ("Schema2", Schema::default()),
1✔
558
                        ("Schema3", Schema::default()),
1✔
559
                    ])
1✔
560
                    .into(),
1✔
561
            ))
1✔
562
            .into();
1✔
563
        can_resolve_api(api);
1✔
564
    }
1✔
565

566
    #[test]
1✔
567
    #[should_panic(expected = "MissingSchema")]
568
    fn detect_unresolvable_response() {
1✔
569
        let api: OpenApi = OpenApiBuilder::new()
1✔
570
            .paths(
1✔
571
                PathsBuilder::new().path(
1✔
572
                    "asdf",
1✔
573
                    PathItemBuilder::new()
1✔
574
                        .operation(
1✔
575
                            PathItemType::Post,
1✔
576
                            OperationBuilder::new().response(
1✔
577
                                "200",
1✔
578
                                ResponseBuilder::new().content(
1✔
579
                                    "application/json",
1✔
580
                                    ContentBuilder::new()
1✔
581
                                        .schema(Ref::from_schema_name("MissingSchema"))
1✔
582
                                        .into(),
1✔
583
                                ),
1✔
584
                            ),
1✔
585
                        )
1✔
586
                        .into(),
1✔
587
                ),
1✔
588
            )
1✔
589
            .components(Some(
1✔
590
                ComponentsBuilder::new()
1✔
591
                    .schemas_from_iter([
1✔
592
                        ("Schema1", Schema::default()),
1✔
593
                        ("Schema2", Schema::default()),
1✔
594
                        ("Schema3", Schema::default()),
1✔
595
                    ])
1✔
596
                    .into(),
1✔
597
            ))
1✔
598
            .into();
1✔
599
        can_resolve_api(api);
1✔
600
    }
1✔
601

602
    #[derive(Deserialize)]
6✔
603
    struct DummyQueryParams {
604
        #[serde(rename = "x")]
605
        _x: String,
606
    }
607

608
    // adding path and query parameter to ensure parameter insertion works
609
    #[post("/test/{id}")]
2✔
610
    #[allow(clippy::unused_async)] // the function signature of request handlers requires it
611
    async fn dummy_handler(
1✔
612
        _id: web::Path<u32>,
1✔
613
        _params: web::Query<DummyQueryParams>,
1✔
614
        _body: web::Json<Volume>,
1✔
615
    ) -> impl Responder {
1✔
616
        HttpResponse::Ok()
1✔
617
    }
1✔
618

619
    async fn dummy_send_test_request<C: SimpleApplicationContext>(
2✔
620
        req: TestRequest,
2✔
621
        ctx: C,
2✔
622
    ) -> ServiceResponse {
2✔
623
        let app = actix_web::test::init_service(
2✔
624
            App::new()
2✔
625
                .app_data(web::Data::new(ctx))
2✔
626
                .wrap(
2✔
627
                    middleware::ErrorHandlers::default()
2✔
628
                        .handler(http::StatusCode::NOT_FOUND, render_404)
2✔
629
                        .handler(http::StatusCode::METHOD_NOT_ALLOWED, render_405),
2✔
630
                )
2✔
631
                .configure(configure_extractors)
2✔
632
                .service(dummy_handler),
2✔
633
        )
2✔
634
        .await;
×
635
        actix_web::test::call_service(&app, req.to_request())
2✔
636
            .await
×
637
            .map_into_boxed_body()
2✔
638
    }
2✔
639

640
    async fn run_dummy_example(example: serde_json::Value) {
2✔
641
        can_run_examples(
2✔
642
            OpenApiBuilder::new()
2✔
643
                .paths(
2✔
644
                    PathsBuilder::new().path(
2✔
645
                        "/test/{id}",
2✔
646
                        PathItemBuilder::new()
2✔
647
                            .operation(
2✔
648
                                PathItemType::Post,
2✔
649
                                OperationBuilder::new()
2✔
650
                                    .parameter(
2✔
651
                                        ParameterBuilder::new()
2✔
652
                                            .name("id")
2✔
653
                                            .parameter_in(ParameterIn::Path)
2✔
654
                                            .schema(Some(RefOr::T(
2✔
655
                                                ObjectBuilder::new()
2✔
656
                                                    .schema_type(SchemaType::Integer)
2✔
657
                                                    .format(Some(SchemaFormat::KnownFormat(
2✔
658
                                                        KnownFormat::Int32,
2✔
659
                                                    )))
2✔
660
                                                    .into(),
2✔
661
                                            ))),
2✔
662
                                    )
2✔
663
                                    .parameter(
2✔
664
                                        ParameterBuilder::new()
2✔
665
                                            .name("x")
2✔
666
                                            .parameter_in(ParameterIn::Query)
2✔
667
                                            .schema(Some(RefOr::T(
2✔
668
                                                Object::with_type(SchemaType::String).into(),
2✔
669
                                            ))),
2✔
670
                                    )
2✔
671
                                    .request_body(Some(
2✔
672
                                        RequestBodyBuilder::new()
2✔
673
                                            .content(
2✔
674
                                                "application/json",
2✔
675
                                                ContentBuilder::new()
2✔
676
                                                    .schema(Volume::schema().1)
2✔
677
                                                    .example(Some(example))
2✔
678
                                                    .into(),
2✔
679
                                            )
2✔
680
                                            .into(),
2✔
681
                                    )),
2✔
682
                            )
2✔
683
                            .into(),
2✔
684
                    ),
2✔
685
                )
2✔
686
                .components(Some(
2✔
687
                    ComponentsBuilder::new()
2✔
688
                        .schemas_from_iter([
2✔
689
                            ("Schema1", Schema::default()),
2✔
690
                            ("Schema2", Schema::default()),
2✔
691
                            ("Schema3", Schema::default()),
2✔
692
                        ])
2✔
693
                        .into(),
2✔
694
                ))
2✔
695
                .into(),
2✔
696
            move || async move {
2✔
697
                let ctx = InMemoryContext::test_default();
2✔
698
                let session_id = ctx.default_session_ref().await.id();
2✔
699
                (ctx, session_id)
2✔
700
            },
2✔
701
            dummy_send_test_request,
2✔
702
        )
2✔
703
        .await;
×
704
    }
1✔
705

706
    #[tokio::test]
1✔
707
    #[should_panic(expected = "BodyDeserializeError")]
708
    async fn detects_bodydeserializeerror() {
1✔
709
        run_dummy_example(json!({"name": "note-path_field_missing"})).await;
1✔
710
    }
711

712
    #[tokio::test]
1✔
713
    async fn successfull_example_run() {
1✔
714
        run_dummy_example(json!({"name": "Files", "path": "/path/to/files"})).await;
1✔
715
    }
716
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc