openzeppelin_relayer/api/routes/
network.rs

1//! This module defines the HTTP routes for network operations.
2//! It includes handlers for listing, retrieving, and updating network configurations.
3//! The routes are integrated with the Actix-web framework and interact with the network controller.
4
5use crate::{
6    api::controllers::network,
7    models::{DefaultAppState, PaginationQuery, UpdateNetworkRequest},
8};
9use actix_web::{get, patch, web, Responder};
10
11/// Lists all networks with pagination support.
12#[get("/networks")]
13async fn list_networks(
14    query: web::Query<PaginationQuery>,
15    data: web::ThinData<DefaultAppState>,
16) -> impl Responder {
17    network::list_networks(query.into_inner(), data).await
18}
19
20/// Retrieves details of a specific network by ID.
21#[get("/networks/{network_id}")]
22async fn get_network(
23    network_id: web::Path<String>,
24    data: web::ThinData<DefaultAppState>,
25) -> impl Responder {
26    network::get_network(network_id.into_inner(), data).await
27}
28
29/// Updates a network's configuration.
30/// Currently supports updating RPC URLs only. Can be extended to support other fields.
31#[patch("/networks/{network_id}")]
32async fn update_network(
33    network_id: web::Path<String>,
34    request: web::Json<UpdateNetworkRequest>,
35    data: web::ThinData<DefaultAppState>,
36) -> impl Responder {
37    network::update_network(network_id.into_inner(), request.into_inner(), data).await
38}
39
40/// Initializes the routes for the network module.
41pub fn init(cfg: &mut web::ServiceConfig) {
42    // Register routes with literal segments before routes with path parameters
43    cfg.service(update_network); // /networks/{network_id}
44    cfg.service(get_network); // /networks/{network_id}
45    cfg.service(list_networks); // /networks
46}
47
48#[cfg(test)]
49mod tests {
50    use super::*;
51    use crate::utils::mocks::mockutils::create_mock_app_state;
52    use actix_web::{http::StatusCode, test, App};
53
54    // ============================================
55    // Route Registration Tests
56    // ============================================
57
58    #[actix_web::test]
59    async fn test_network_routes_are_registered() {
60        // Arrange - Create app with network routes
61        let app_state = create_mock_app_state(None, None, None, None, None, None).await;
62        let app = test::init_service(
63            App::new()
64                .app_data(web::Data::new(app_state))
65                .configure(init),
66        )
67        .await;
68
69        // Test GET /networks - should not return 404 (route exists)
70        let req = test::TestRequest::get().uri("/networks").to_request();
71        let resp = test::call_service(&app, req).await;
72        assert_ne!(
73            resp.status(),
74            StatusCode::NOT_FOUND,
75            "GET /networks route not registered"
76        );
77
78        // Test GET /networks/{network_id} - should not return 404
79        let req = test::TestRequest::get()
80            .uri("/networks/evm:sepolia")
81            .to_request();
82        let resp = test::call_service(&app, req).await;
83        assert_ne!(
84            resp.status(),
85            StatusCode::NOT_FOUND,
86            "GET /networks/{{network_id}} route not registered"
87        );
88
89        // Test PATCH /networks/{network_id} - should not return 404
90        let req = test::TestRequest::patch()
91            .uri("/networks/evm:sepolia")
92            .set_json(serde_json::json!({
93                "rpc_urls": ["https://rpc.example.com"]
94            }))
95            .to_request();
96        let resp = test::call_service(&app, req).await;
97        assert_ne!(
98            resp.status(),
99            StatusCode::NOT_FOUND,
100            "PATCH /networks/{{network_id}} route not registered"
101        );
102    }
103
104    #[actix_web::test]
105    async fn test_network_routes_with_query_params() {
106        // Arrange - Create app with network routes
107        let app_state = create_mock_app_state(None, None, None, None, None, None).await;
108        let app = test::init_service(
109            App::new()
110                .app_data(web::Data::new(app_state))
111                .configure(init),
112        )
113        .await;
114
115        // Test GET /networks with pagination parameters
116        let req = test::TestRequest::get()
117            .uri("/networks?page=1&per_page=10")
118            .to_request();
119        let resp = test::call_service(&app, req).await;
120        assert_ne!(
121            resp.status(),
122            StatusCode::NOT_FOUND,
123            "GET /networks with query params route not registered"
124        );
125    }
126
127    #[actix_web::test]
128    async fn test_network_routes_with_special_characters_in_path() {
129        // Network IDs use format like "evm:sepolia" which contains a colon
130        let app_state = create_mock_app_state(None, None, None, None, None, None).await;
131        let app = test::init_service(
132            App::new()
133                .app_data(web::Data::new(app_state))
134                .configure(init),
135        )
136        .await;
137
138        // Test that route handles colons in path parameters
139        let req = test::TestRequest::get()
140            .uri("/networks/evm:mainnet")
141            .to_request();
142        let resp = test::call_service(&app, req).await;
143        assert_ne!(
144            resp.status(),
145            StatusCode::NOT_FOUND,
146            "GET /networks/evm:mainnet route should handle colon in path"
147        );
148
149        let req = test::TestRequest::get()
150            .uri("/networks/solana:devnet")
151            .to_request();
152        let resp = test::call_service(&app, req).await;
153        assert_ne!(
154            resp.status(),
155            StatusCode::NOT_FOUND,
156            "GET /networks/solana:devnet route should handle colon in path"
157        );
158
159        let req = test::TestRequest::get()
160            .uri("/networks/stellar:testnet")
161            .to_request();
162        let resp = test::call_service(&app, req).await;
163        assert_ne!(
164            resp.status(),
165            StatusCode::NOT_FOUND,
166            "GET /networks/stellar:testnet route should handle colon in path"
167        );
168    }
169
170    #[actix_web::test]
171    async fn test_patch_network_route_accepts_json() {
172        let app_state = create_mock_app_state(None, None, None, None, None, None).await;
173        let app = test::init_service(
174            App::new()
175                .app_data(web::Data::new(app_state))
176                .configure(init),
177        )
178        .await;
179
180        // Test PATCH with valid JSON payload structure
181        let req = test::TestRequest::patch()
182            .uri("/networks/evm:sepolia")
183            .set_json(serde_json::json!({
184                "rpc_urls": ["https://rpc1.example.com", "https://rpc2.example.com"]
185            }))
186            .to_request();
187        let resp = test::call_service(&app, req).await;
188        assert_ne!(
189            resp.status(),
190            StatusCode::NOT_FOUND,
191            "PATCH /networks/{{network_id}} with JSON body route not registered"
192        );
193    }
194
195    #[actix_web::test]
196    async fn test_patch_network_route_accepts_weighted_rpc_urls() {
197        let app_state = create_mock_app_state(None, None, None, None, None, None).await;
198        let app = test::init_service(
199            App::new()
200                .app_data(web::Data::new(app_state))
201                .configure(init),
202        )
203        .await;
204
205        // Test PATCH with weighted RPC URL format
206        let req = test::TestRequest::patch()
207            .uri("/networks/evm:sepolia")
208            .set_json(serde_json::json!({
209                "rpc_urls": [
210                    {"url": "https://primary.example.com", "weight": 80},
211                    {"url": "https://backup.example.com", "weight": 20}
212                ]
213            }))
214            .to_request();
215        let resp = test::call_service(&app, req).await;
216        assert_ne!(
217            resp.status(),
218            StatusCode::NOT_FOUND,
219            "PATCH /networks/{{network_id}} with weighted RPC URLs route not registered"
220        );
221    }
222
223    #[actix_web::test]
224    async fn test_patch_network_route_accepts_mixed_rpc_url_formats() {
225        let app_state = create_mock_app_state(None, None, None, None, None, None).await;
226        let app = test::init_service(
227            App::new()
228                .app_data(web::Data::new(app_state))
229                .configure(init),
230        )
231        .await;
232
233        // Test PATCH with mixed RPC URL formats (strings and objects)
234        let req = test::TestRequest::patch()
235            .uri("/networks/evm:sepolia")
236            .set_json(serde_json::json!({
237                "rpc_urls": [
238                    "https://simple.example.com",
239                    {"url": "https://weighted.example.com", "weight": 50}
240                ]
241            }))
242            .to_request();
243        let resp = test::call_service(&app, req).await;
244        assert_ne!(
245            resp.status(),
246            StatusCode::NOT_FOUND,
247            "PATCH /networks/{{network_id}} with mixed RPC URL formats route not registered"
248        );
249    }
250}