workspacer_cratesio_mock/
lib.rs

1// ---------------- [ File: workspacer-cratesio-mock/src/lib.rs ]
2#[macro_use] mod imports; use imports::*;
3
4x!{app_state}
5x!{crates_db}
6x!{protocol}
7x!{publish}
8x!{stored_crate}
9
10#[cfg(test)]
11mod test_publish_integration {
12    use super::*;
13    use rocket::http::{ContentType, Status};
14    use rocket::local::blocking::Client;
15    use rocket::serde::json::serde_json;
16    use tracing::{trace, debug, info};
17    use crate::app_state::AppStateBuilder;
18    use crate::crates_db::MockCratesDb;
19    
20    
21    
22
23    // We stand up a fresh Rocket for each test to ensure 
24    // we're truly testing the interface in isolation.
25    fn setup_rocket() -> Client {
26        trace!("Setting up test rocket instance.");
27
28        let state = AppStateBuilder::default()
29            .db(Arc::new(AsyncMutex::new(MockCratesDb::default())))
30            .build()
31            .unwrap();
32
33        let rocket = rocket::build()
34            .manage(state)
35            .mount("/", rocket::routes![publish_new])
36            .register("/", rocket::catchers![not_found]);
37
38        Client::tracked(rocket).expect("valid rocket instance")
39    }
40
41    #[traced_test]
42    fn test_successful_publish() {
43        trace!("Starting test_successful_publish.");
44
45        let client = setup_rocket();
46        let body = serde_json::json!({
47            "name": "my-crate",
48            "vers": "0.1.0",
49            "description": "A test crate."
50        })
51        .to_string();
52
53        debug!("POST body: {}", body);
54
55        let response = client
56            .post("/api/v1/crates/new")
57            .header(ContentType::JSON)
58            .body(body)
59            .dispatch();
60
61        info!("Response status: {:?}", response.status());
62        assert_eq!(response.status(), Status::Ok);
63
64        let response_json = response.into_json::<serde_json::Value>()
65            .expect("Response should be valid JSON");
66        debug!("Response JSON: {:?}", response_json);
67
68        // Expecting structure like: {"ok":true}
69        assert!(response_json.get("ok").and_then(|v| v.as_bool()).unwrap_or(false));
70    }
71
72    #[traced_test]
73    fn test_missing_description() {
74        trace!("Starting test_missing_description.");
75
76        let client = setup_rocket();
77        let body = serde_json::json!({
78            "name": "my-crate",
79            "vers": "0.2.0"
80            // no "description"
81        })
82        .to_string();
83
84        debug!("POST body: {}", body);
85
86        let response = client
87            .post("/api/v1/crates/new")
88            .header(ContentType::JSON)
89            .body(body)
90            .dispatch();
91
92        info!("Response status: {:?}", response.status());
93        assert_eq!(response.status(), Status::BadRequest);
94
95        let response_json = response.into_json::<serde_json::Value>()
96            .expect("Response should be valid JSON");
97        debug!("Response JSON: {:?}", response_json);
98
99        // Expecting structure like: {"errors":[{"detail":"..."}]}
100        let errors = response_json
101            .get("errors")
102            .and_then(|v| v.as_array())
103            .expect("Expected 'errors' array in response");
104        let detail = errors[0]
105            .get("detail")
106            .and_then(|v| v.as_str())
107            .unwrap_or("");
108        assert!(
109            detail.contains("description"),
110            "Should complain about missing description"
111        );
112    }
113
114    #[traced_test]
115    fn test_already_published() {
116        trace!("Starting test_already_published.");
117
118        let client = setup_rocket();
119
120        // First publish
121        let body = serde_json::json!({
122            "name": "my-crate",
123            "vers": "1.0.0",
124            "description": "Initial publish."
125        })
126        .to_string();
127
128        let first_resp = client
129            .post("/api/v1/crates/new")
130            .header(ContentType::JSON)
131            .body(body.clone())
132            .dispatch();
133        assert_eq!(first_resp.status(), Status::Ok);
134
135        // Second publish of the same exact version
136        let second_resp = client
137            .post("/api/v1/crates/new")
138            .header(ContentType::JSON)
139            .body(body)
140            .dispatch();
141
142        info!("Response status: {:?}", second_resp.status());
143        assert_eq!(second_resp.status(), Status::BadRequest);
144
145        let response_json = second_resp.into_json::<serde_json::Value>()
146            .expect("Response should be valid JSON");
147        debug!("Response JSON: {:?}", response_json);
148
149        let errors = response_json
150            .get("errors")
151            .and_then(|v| v.as_array())
152            .expect("Expected 'errors' array in response");
153        let detail = errors[0]
154            .get("detail")
155            .and_then(|v| v.as_str())
156            .unwrap_or("");
157        assert!(
158            detail.contains("already exists"),
159            "Should complain about crate already existing"
160        );
161    }
162
163    #[traced_test]
164    fn test_invalid_json_body() {
165        trace!("Starting test_invalid_json_body.");
166
167        let client = setup_rocket();
168
169        // Provide invalid JSON
170        let body = "{ invalid-json".to_string();
171        debug!("POST body: {}", body);
172
173        let response = client
174            .post("/api/v1/crates/new")
175            .header(ContentType::JSON)
176            .body(body)
177            .dispatch();
178
179        info!("Response status: {:?}", response.status());
180        assert_eq!(response.status(), Status::BadRequest);
181
182        let response_json = response.into_json::<serde_json::Value>()
183            .expect("Response should be valid JSON");
184        debug!("Response JSON: {:?}", response_json);
185
186        let errors = response_json
187            .get("errors")
188            .and_then(|v| v.as_array())
189            .expect("Expected 'errors' array in response");
190        let detail = errors[0]
191            .get("detail")
192            .and_then(|v| v.as_str())
193            .unwrap_or("");
194        assert!(
195            detail.contains("invalid JSON"),
196            "Should complain about invalid JSON"
197        );
198    }
199
200    #[traced_test]
201    fn test_404_catcher() {
202        trace!("Starting test_404_catcher.");
203
204        let client = setup_rocket();
205        let response = client.get("/this/does/not/exist").dispatch();
206        info!("Response status: {:?}", response.status());
207        assert_eq!(response.status(), Status::NotFound);
208
209        let response_json = response.into_json::<serde_json::Value>()
210            .expect("Response should be valid JSON");
211        debug!("Response JSON: {:?}", response_json);
212
213        let errors = response_json
214            .get("errors")
215            .and_then(|v| v.as_array())
216            .expect("Expected 'errors' array in response");
217        let detail = errors[0]
218            .get("detail")
219            .and_then(|v| v.as_str())
220            .unwrap_or("");
221        assert!(
222            detail.contains("No route for /this/does/not/exist"),
223            "Should contain a 404 message with requested URI"
224        );
225    }
226
227    #[traced_test]
228    fn test_too_large_body() {
229        trace!("Starting test_too_large_body.");
230
231        // We'll craft a body exceeding 10MiB (~ 10,485,760 bytes).
232        // Let's do 11MiB to be safe. We'll expect a BadRequest 
233        // from rocket's 'read_to_end' limit in publish_new.
234        let size = 11 * 1024 * 1024; 
235        let oversized_body = "A".repeat(size);
236
237        let client = setup_rocket();
238        let response = client
239            .post("/api/v1/crates/new")
240            .header(ContentType::JSON)
241            .body(oversized_body)
242            .dispatch();
243
244        info!("Response status: {:?}", response.status());
245        // Typically rocket yields 413 (Payload Too Large) or 400 
246        // depending on the version and data limit handling.
247        // In our code, we handle any read error as BadRequest.
248        assert_eq!(response.status(), Status::BadRequest);
249
250        let response_json = response.into_json::<serde_json::Value>()
251            .expect("Response should be valid JSON");
252        debug!("Response JSON: {:?}", response_json);
253
254        let errors = response_json
255            .get("errors")
256            .and_then(|v| v.as_array())
257            .expect("Expected 'errors' array in response");
258        let detail = errors[0]
259            .get("detail")
260            .and_then(|v| v.as_str())
261            .unwrap_or("");
262        assert!(
263            detail.contains("Failed reading request body"),
264            "Should mention failed reading body for large input"
265        );
266    }
267}