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

geo-engine / BioIS / 22675492559

04 Mar 2026 03:10PM UTC coverage: 80.076% (-8.4%) from 88.45%
22675492559

push

github

web-flow
Merge pull request #6 from geo-engine/openapi

refactor: ui into main

154 of 253 branches covered (60.87%)

Branch coverage included in aggregate %.

386 of 549 new or added lines in 22 files covered. (70.31%)

2 existing lines in 1 file now uncovered.

1530 of 1850 relevant lines covered (82.7%)

1.83 hits per line

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

84.76
/backend/src/handler.rs
1
use crate::auth::{AuthCodeResponse, UserSession};
2
use anyhow::Context;
3
use axum::{
4
    Json,
5
    extract::{Query, State},
6
    http::StatusCode,
7
    response::IntoResponse,
8
};
9
use geoengine_openapi_client::apis::{
10
    configuration::Configuration,
11
    session_api::{oidc_init, oidc_login},
12
};
13
use ogcapi::{
14
    services::{self as ogcapi_services},
15
    types::common::Exception,
16
};
17
use serde::Deserialize;
18
use url::Url;
19
use utoipa::IntoParams;
20
use utoipa_axum::{router::OpenApiRouter, routes};
21

22
pub fn auth_router() -> OpenApiRouter<Configuration> {
1✔
23
    OpenApiRouter::new()
1✔
24
        .routes(routes!(auth_handler))
1✔
25
        .routes(routes!(auth_request_url_handler))
1✔
26
}
1✔
27

28
#[utoipa::path(get, path = "/health", responses((status = NO_CONTENT)))]
29
pub async fn health_handler() -> StatusCode {
1✔
30
    StatusCode::NO_CONTENT
1✔
31
}
1✔
32

33
#[utoipa::path(post, path = "/accessTokenLogin", tag = "User",
1✔
34
    params(AuthRequestUrlParams),
1✔
35
    responses(
1✔
36
        (
1✔
37
            status = OK,
1✔
38
            description = "The OIDC login flow was successful, and a user session has been created.",
1✔
39
            body = UserSession
1✔
40
        ),
1✔
41
        (
1✔
42
            status = INTERNAL_SERVER_ERROR,
1✔
43
            description = "A server error occurred.", 
1✔
44
            body = Exception,
1✔
45
            example = json!(Exception::new_from_status(500))
1✔
46
        )
47
    )
48
)]
49
async fn auth_handler(
1✔
50
    State(api_config): State<Configuration>,
1✔
51
    Query(AuthRequestUrlParams { redirect_uri }): Query<AuthRequestUrlParams>,
1✔
52
    Json(auth_code_response): Json<AuthCodeResponse>,
1✔
53
) -> ogcapi_services::Result<Json<UserSession>> {
1✔
54
    let user_session = oidc_login(
1✔
55
        &api_config,
1✔
56
        redirect_uri.as_str(),
1✔
57
        auth_code_response.into(),
1✔
58
    )
1✔
59
    .await
1✔
60
    .context("Failed to perform OIDC login")?;
1✔
61

62
    Ok(Json(user_session.into()))
1✔
63
}
1✔
64

65
#[derive(Deserialize, IntoParams)]
66
#[serde(rename_all = "camelCase")]
67
struct AuthRequestUrlParams {
68
    /// The URI to which the identity provider should redirect after successful authentication.
69
    redirect_uri: Url,
70
}
71

72
/// Generates a URL for initiating the OIDC code flow, which the frontend can use to redirect the user to the identity provider's login page.
73
#[utoipa::path(get, path = "/authenticationRequestUrl", tag = "User",
1✔
74
    params(AuthRequestUrlParams),
1✔
75
    responses(
1✔
76
        (
1✔
77
            status = OK,
1✔
78
            description = "A URL for initiating the OIDC code flow.",
1✔
79
            body = Url
1✔
80
        ),
1✔
81
        (
1✔
82
            status = INTERNAL_SERVER_ERROR,
1✔
83
            description = "A server error occurred.", 
1✔
84
            body = Exception,
1✔
85
            example = json!(Exception::new_from_status(500))
1✔
86
        )
87
    )
88
)]
NEW
89
async fn auth_request_url_handler(
×
NEW
90
    State(api_config): State<Configuration>,
×
NEW
91
    Query(AuthRequestUrlParams { redirect_uri }): Query<AuthRequestUrlParams>,
×
NEW
92
) -> ogcapi_services::Result<UrlResponse> {
×
NEW
93
    let auth_code_flow_request_url = oidc_init(&api_config, redirect_uri.as_str())
×
UNCOV
94
        .await
×
UNCOV
95
        .context("Failed to perform OIDC login")?;
×
96

NEW
97
    let auth_code_flow_request_url: Url = auth_code_flow_request_url
×
NEW
98
        .url
×
NEW
99
        .parse()
×
NEW
100
        .context("Failed to parse OIDC authentication request URL")?;
×
101

NEW
102
    Ok(UrlResponse(auth_code_flow_request_url))
×
NEW
103
}
×
104

105
struct UrlResponse(Url);
106

107
impl IntoResponse for UrlResponse {
NEW
108
    fn into_response(self) -> axum::response::Response {
×
NEW
109
        String::from(self.0).into_response()
×
NEW
110
    }
×
111
}
112

113
#[cfg(test)]
114
mod tests {
115
    use super::*;
116
    use crate::auth::AuthCodeResponse;
117
    use crate::config::GeoEngineInstance;
118
    use axum::extract::{Query, State};
119
    use axum::routing::get;
120
    use axum::{Json, Router};
121
    use axum::{body::Body, http::Request};
122
    use httptest::matchers::request::method;
123
    use httptest::{Expectation, Server, responders::json_encoded};
124
    use reqwest::StatusCode;
125
    use serde_json::json;
126
    use tower::ServiceExt;
127
    use url::Url;
128

129
    #[tokio::test]
130
    async fn test_health_route() {
1✔
131
        let app = Router::new().route("/health", get(health_handler));
1✔
132
        let request = Request::builder()
1✔
133
            .uri("/health")
1✔
134
            .body(Body::empty())
1✔
135
            .unwrap();
1✔
136

137
        let response = app.oneshot(request).await.unwrap();
1✔
138
        assert_eq!(response.status(), StatusCode::NO_CONTENT);
1✔
139
    }
1✔
140

141
    #[tokio::test]
142
    async fn test_auth_handler_with_mock_server() {
1✔
143
        // start mock server
144
        let server = Server::run();
1✔
145

146
        // respond to oidcLogin under an `/api` base with a valid user session
147
        server.expect(
1✔
148
            Expectation::matching(method("POST"))
1✔
149
                .respond_with(json_encoded(json!({
1✔
150
                    "id": "d1322969-5ada-4a2c-bacf-a3045383ba41",
1✔
151
                    "user": { "id": "9273bb02-95a6-49fe-b1c6-a32ff171d4a3", "email": "foo@example.com", "realName": "Max Muster" },
1✔
152
                    "created": "2020-01-01T00:00:00Z",
1✔
153
                    "validUntil": "2021-01-01T00:00:00Z",
1✔
154
                    "project": null,
1✔
155
                    "view": null,
1✔
156
                    "roles": []
1✔
157
                })))
158
        );
159

160
        let api_config = GeoEngineInstance {
1✔
161
            base_url: Url::parse(&server.url_str("")).expect("valid url"),
1✔
162
        }
1✔
163
        .api_config(None);
1✔
164

165
        // build test inputs
166
        let redirect = "http://example.com/redirect".to_string();
1✔
167
        let auth_code_response = AuthCodeResponse {
1✔
168
            code: String::new(),
1✔
169
            session_state: String::new(),
1✔
170
            state: String::new(),
1✔
171
        };
1✔
172

173
        // call handler
174
        let res = auth_handler(
1✔
175
            State(api_config),
1✔
176
            Query(AuthRequestUrlParams {
1✔
177
                redirect_uri: Url::parse(&redirect).unwrap(),
1✔
178
            }),
1✔
179
            Json(auth_code_response),
1✔
180
        )
1✔
181
        .await;
1✔
182

183
        assert!(res.is_ok(), "expected Ok(UserSession) from auth_handler");
1✔
184
    }
1✔
185
}
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