1
use axum::{
2
    body::Body,
3
    http::{Request, StatusCode},
4
};
5
use tower::ServiceExt;
6

            
7
use crate::common::{create_test_app_state, test_limiter};
8
use web::route::create_api_router;
9

            
10
// PUBLIC API ROUTES TESTS
11
#[supp_macro::shared_runtime_test]
12
1
async fn test_public_routes_accessible() {
13
1
    let app_state = create_test_app_state().await;
14
1
    let app = create_api_router(app_state.clone(), test_limiter()).with_state(app_state);
15

            
16
    // Test version endpoint
17
1
    let response = app
18
1
        .clone()
19
1
        .oneshot(
20
1
            Request::builder()
21
1
                .uri("/version")
22
1
                .body(Body::empty())
23
1
                .unwrap(),
24
1
        )
25
1
        .await
26
1
        .unwrap();
27

            
28
1
    assert_eq!(response.status(), StatusCode::OK);
29

            
30
    // Test logout endpoint (requires auth, so expect 401/403)
31
1
    let response = app
32
1
        .oneshot(
33
1
            Request::builder()
34
1
                .uri("/auth/logout")
35
1
                .body(Body::empty())
36
1
                .unwrap(),
37
1
        )
38
1
        .await
39
1
        .unwrap();
40

            
41
    // Logout requires authentication, so should return 401 or 403
42
1
    assert!(
43
1
        response.status() == StatusCode::UNAUTHORIZED || response.status() == StatusCode::FORBIDDEN
44
    );
45
}
46

            
47
#[supp_macro::shared_runtime_test]
48
1
async fn test_protected_routes_require_auth() {
49
1
    let app_state = create_test_app_state().await;
50
1
    let app = create_api_router(app_state.clone(), test_limiter()).with_state(app_state);
51

            
52
1
    let protected_routes = vec!["/auth/refresh"];
53

            
54
1
    for route in protected_routes {
55
1
        let response = app
56
1
            .clone()
57
1
            .oneshot(
58
1
                Request::builder()
59
1
                    .method("GET")
60
1
                    .uri(route)
61
1
                    .body(Body::empty())
62
1
                    .unwrap(),
63
1
            )
64
1
            .await
65
1
            .unwrap();
66

            
67
1
        assert!(
68
1
            response.status() == StatusCode::UNAUTHORIZED
69
                || response.status() == StatusCode::FORBIDDEN
70
                || response.status().is_server_error(),
71
            "Route {} should require authentication, got status: {}",
72
            route,
73
            response.status()
74
        );
75
    }
76
}
77

            
78
/// The password endpoint must sit behind `auth`. It is the second endpoint in
79
/// the app that verifies a password, so an unauthenticated caller reaching it
80
/// would be an unthrottled credential oracle on someone else's account.
81
#[supp_macro::shared_runtime_test]
82
1
async fn test_password_change_requires_auth() {
83
1
    let app_state = create_test_app_state().await;
84
1
    let app = create_api_router(app_state.clone(), test_limiter()).with_state(app_state);
85

            
86
1
    let response = app
87
1
        .oneshot(
88
1
            Request::builder()
89
1
                .method("POST")
90
1
                .uri("/auth/password")
91
1
                .header("content-type", "application/json")
92
1
                .body(Body::from(r#"{"current_password":"a","new_password":"b"}"#))
93
1
                .unwrap(),
94
1
        )
95
1
        .await
96
1
        .unwrap();
97

            
98
1
    assert!(
99
1
        response.status() == StatusCode::UNAUTHORIZED || response.status() == StatusCode::FORBIDDEN,
100
        "unauthenticated password change must be refused, got: {}",
101
        response.status()
102
    );
103
}