Coverage Report

Created: 2026-03-25 23:22

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/home/runner/work/tenet/tenet/tenet-api/src/routes/projects/components.rs
Line
Count
Source
1
use axum::{
2
    Json, Router,
3
    extract::{Path, State},
4
    routing::get,
5
};
6
use tenet_aws::{
7
    models::component::{CreateArchitectureComponentRequest, UpdateArchitectureComponentRequest},
8
    services::crud::ParentScope,
9
};
10
11
use crate::http::{AppState, JsonResponse, ProjectRequest, UserInfo};
12
use crate::routes::policy::require_org_admin;
13
14
use super::shared::{ContainerComponentPath, ContainerPath};
15
16
1
fn container_scope(user: &UserInfo, path: &ContainerPath) -> ParentScope {
17
1
    ParentScope {
18
1
        org_id: path.org_id.clone(),
19
1
        project_id: path.project_id.clone(),
20
1
        parent_id: path.container_id.clone(),
21
1
        user_id: user.id.clone(),
22
1
    }
23
1
}
24
25
1
fn component_scope(user: &UserInfo, path: &ContainerComponentPath) -> ParentScope {
26
1
    ParentScope {
27
1
        org_id: path.org_id.clone(),
28
1
        project_id: path.project_id.clone(),
29
1
        parent_id: path.container_id.clone(),
30
1
        user_id: user.id.clone(),
31
1
    }
32
1
}
33
34
1
fn component_id(path: &ContainerComponentPath) -> tenet_aws::models::component::ComponentId {
35
1
    path.component_id.clone()
36
1
}
37
38
3
pub fn router() -> axum::Router<AppState> {
39
3
    Router::new()
40
3
        .route(
41
3
            "/organisations/{org_id}/projects/{project_id}/containers/{container_id}/components",
42
3
            get(list_components).post(create_component),
43
        )
44
3
        .route(
45
3
            "/organisations/{org_id}/projects/{project_id}/containers/{container_id}/components/{component_id}",
46
3
            get(get_component).put(update_component).delete(delete_component),
47
        )
48
3
}
49
50
0
async fn list_components(
51
0
    State(state): State<AppState>,
52
0
    req: ProjectRequest<ContainerPath>,
53
0
) -> axum::response::Response {
54
0
    let ProjectRequest { user, path } = req;
55
0
    let scope = container_scope(&user, &path);
56
0
    JsonResponse::ok_result_with_context(
57
0
        state
58
0
            .component
59
0
            .list_architecture_components_for_container(
60
0
                &scope.org_id,
61
0
                &scope.project_id,
62
0
                &scope.parent_id,
63
0
            )
64
0
            .await,
65
        "Failed to list components",
66
    )
67
0
}
68
69
0
async fn create_component(
70
0
    State(state): State<AppState>,
71
0
    req: ProjectRequest<ContainerPath>,
72
0
    Json(body): Json<CreateArchitectureComponentRequest>,
73
0
) -> axum::response::Response {
74
0
    let ProjectRequest { user, path } = req;
75
0
    let scope = container_scope(&user, &path);
76
0
    JsonResponse::created_result_with_context(
77
0
        state
78
0
            .component
79
0
            .create_component(
80
0
                &scope.org_id,
81
0
                &scope.project_id,
82
0
                &scope.user_id,
83
0
                &scope.parent_id,
84
0
                &body,
85
0
            )
86
0
            .await,
87
        "Failed to create component",
88
    )
89
0
}
90
91
0
async fn get_component(
92
0
    State(state): State<AppState>,
93
0
    req: ProjectRequest<ContainerComponentPath>,
94
0
) -> axum::response::Response {
95
0
    let ProjectRequest { user, path } = req;
96
0
    let scope = component_scope(&user, &path);
97
0
    let id = component_id(&path);
98
0
    JsonResponse::ok_result_with_context(
99
0
        state
100
0
            .component
101
0
            .get_architecture_component_scoped_to_container(
102
0
                &scope.org_id,
103
0
                &scope.project_id,
104
0
                &scope.parent_id,
105
0
                &id,
106
0
            )
107
0
            .await,
108
        "Failed to get component",
109
    )
110
0
}
111
112
0
async fn update_component(
113
0
    State(state): State<AppState>,
114
0
    req: ProjectRequest<ContainerComponentPath>,
115
0
    Json(body): Json<UpdateArchitectureComponentRequest>,
116
0
) -> axum::response::Response {
117
0
    let ProjectRequest { user, path } = req;
118
0
    let scope = component_scope(&user, &path);
119
0
    let id = component_id(&path);
120
0
    JsonResponse::ok_result_with_context(
121
0
        state
122
0
            .component
123
0
            .update_architecture_component_scoped_to_container(
124
0
                &scope.org_id,
125
0
                &scope.project_id,
126
0
                &scope.user_id,
127
0
                &scope.parent_id,
128
0
                &id,
129
0
                body,
130
0
            )
131
0
            .await,
132
        "Failed to update component",
133
    )
134
0
}
135
136
0
async fn delete_component(
137
0
    State(state): State<AppState>,
138
0
    user: UserInfo,
139
0
    Path(path): Path<ContainerComponentPath>,
140
0
) -> axum::response::Response {
141
0
    if let Err(err) = require_org_admin(&state, &path.org_id, &user.id).await {
142
0
        return JsonResponse::error(err);
143
0
    }
144
145
0
    let scope = component_scope(&user, &path);
146
0
    let id = component_id(&path);
147
0
    JsonResponse::no_content_result_with_context(
148
0
        state
149
0
            .component
150
0
            .delete_architecture_component_scoped_to_container(
151
0
                &scope.org_id,
152
0
                &scope.project_id,
153
0
                &scope.user_id,
154
0
                &scope.parent_id,
155
0
                &id,
156
0
            )
157
0
            .await,
158
        "Failed to delete component",
159
    )
160
0
}
161
162
#[cfg(test)]
163
mod tests {
164
    use super::*;
165
    use crate::routes::test_support;
166
167
    #[allure_rs::allure_parent_suite("tenet-api")]
168
    #[allure_rs::allure_test]
169
    #[tokio::test]
170
    async fn test_component_routes_exist() {
171
        let _router = router();
172
    }
173
174
    #[allure_rs::allure_parent_suite("tenet-api")]
175
    #[allure_rs::allure_test]
176
    #[test]
177
    fn test_component_scope_mapping() {
178
        let user = test_support::user("user-3");
179
        let list_path = ContainerPath {
180
            org_id: test_support::org_id(),
181
            project_id: test_support::project_id(),
182
            container_id: test_support::app_id(),
183
        };
184
        let item_path = ContainerComponentPath {
185
            org_id: test_support::org_id(),
186
            project_id: test_support::project_id(),
187
            container_id: test_support::app_id(),
188
            component_id: test_support::library_id(),
189
        };
190
191
        let list_scope = container_scope(&user, &list_path);
192
        let item_scope = component_scope(&user, &item_path);
193
        let id = component_id(&item_path);
194
195
        assert_eq!(list_scope.user_id.to_string(), "user-3");
196
        assert_eq!(
197
            item_scope.project_id.to_string(),
198
            test_support::PROJECT_ID_STR
199
        );
200
        assert_eq!(item_scope.parent_id.to_string(), test_support::APP_ID_STR);
201
        assert_eq!(id.to_string(), test_support::LIB_ID_STR);
202
    }
203
}