1use crate::*;
19use async_trait::async_trait;
20use base64::prelude::*;
21use bytes::Buf;
22use endbasic_std::console::remove_control_chars;
23use endbasic_std::storage::FileAcls;
24use reqwest::Response;
25use reqwest::StatusCode;
26use reqwest::header::HeaderMap;
27use std::cell::RefCell;
28use std::io;
29use std::rc::Rc;
30use std::str;
31use url::Url;
32
33async fn http_response_to_io_error(response: Response) -> io::Error {
35 let status = response.status();
36
37 let kind = match status {
38 StatusCode::OK => panic!("Should not have been called on a successful request"),
39
40 StatusCode::BAD_REQUEST => io::ErrorKind::InvalidInput,
42 StatusCode::FORBIDDEN => io::ErrorKind::PermissionDenied,
43 StatusCode::INSUFFICIENT_STORAGE => io::ErrorKind::Other,
44 StatusCode::INTERNAL_SERVER_ERROR => io::ErrorKind::Other,
45 StatusCode::NOT_FOUND => io::ErrorKind::NotFound,
46 StatusCode::PAYLOAD_TOO_LARGE => io::ErrorKind::InvalidInput,
47 StatusCode::SERVICE_UNAVAILABLE => io::ErrorKind::AddrNotAvailable,
48 StatusCode::UNAUTHORIZED => io::ErrorKind::PermissionDenied,
49
50 _ => io::ErrorKind::Other,
51 };
52
53 match response.text().await {
54 Ok(text) => match serde_json::from_str::<ErrorResponse>(&text) {
55 Ok(response) => io::Error::new(
56 kind,
57 format!("{} (server code: {})", remove_control_chars(response.message), status),
58 ),
59 _ => io::Error::new(
60 kind,
61 format!(
62 "HTTP request returned status {} with text '{}'",
63 status,
64 remove_control_chars(text)
65 ),
66 ),
67 },
68 Err(e) => io::Error::new(
69 kind,
70 format!(
71 "HTTP request returned status {} and failed to get text due to {}",
72 status,
73 remove_control_chars(e.to_string())
74 ),
75 ),
76 }
77}
78
79fn reqwest_error_to_io_error(e: reqwest::Error) -> io::Error {
81 io::Error::other(format!("{}", e))
82}
83
84struct AuthData {
86 username: String,
87 access_token: AccessToken,
88}
89
90pub struct CloudService {
92 api_address: Url,
93 client: reqwest::Client,
94 auth_data: Rc<RefCell<Option<AuthData>>>,
95}
96
97impl CloudService {
98 pub fn new(api_address: &str) -> io::Result<Self> {
100 let url = match Url::parse(api_address) {
101 Ok(url) => url,
102 Err(e) => {
103 return Err(io::Error::new(
104 io::ErrorKind::InvalidInput,
105 format!("Invalid base API address: {}", e),
106 ));
107 }
108 };
109
110 if !(url.path().is_empty() || url.path() == "/") {
111 return Err(io::Error::new(
112 io::ErrorKind::InvalidInput,
113 "Invalid base API address: cannot contain a path".to_owned(),
114 ));
115 }
116
117 let auth_data = Rc::from(RefCell::from(None));
118
119 Ok(Self { api_address: url, client: reqwest::Client::default(), auth_data })
120 }
121
122 fn make_url(&self, path: &str) -> Url {
124 assert!(path.starts_with("api/"));
125 let mut url = self.api_address.clone();
126 assert!(url.path().is_empty() || url.path() == "/");
127 url.set_path(path);
128 url
129 }
130
131 fn default_headers(&self) -> HeaderMap {
133 let mut headers = HeaderMap::new();
134 headers.insert(
135 "x-endbasic-client-version",
136 env!("CARGO_PKG_VERSION")
137 .parse()
138 .expect("Package version should have been serializable"),
139 );
140 headers
141 }
142
143 fn require_auth_data(data: Option<&AuthData>) -> io::Result<&AuthData> {
146 match data.as_ref() {
147 Some(data) => Ok(data),
148 None => {
149 Err(io::Error::new(io::ErrorKind::PermissionDenied, "Not logged in yet".to_owned()))
150 }
151 }
152 }
153}
154
155#[async_trait(?Send)]
156impl Service for CloudService {
157 async fn signup(&mut self, request: &SignupRequest) -> io::Result<()> {
158 let response = self
159 .client
160 .post(self.make_url("api/signup"))
161 .headers(self.default_headers())
162 .header("Content-Type", "application/json")
163 .body(serde_json::to_vec(&request)?)
164 .send()
165 .await
166 .map_err(reqwest_error_to_io_error)?;
167 match response.status() {
168 StatusCode::OK => Ok(()),
169 _ => Err(http_response_to_io_error(response).await),
170 }
171 }
172
173 async fn login(&mut self, username: &str, password: &str) -> io::Result<LoginResponse> {
174 let basic_auth =
177 format!("Basic {}", BASE64_STANDARD.encode(format!("{}:{}", username, password)));
178
179 let response = self
180 .client
181 .post(self.make_url("api/login"))
182 .headers(self.default_headers())
183 .header("Authorization", basic_auth)
184 .header("Content-Length", 0)
185 .send()
186 .await
187 .map_err(reqwest_error_to_io_error)?;
188 match response.status() {
189 StatusCode::OK => {
190 let bytes = response.bytes().await.map_err(reqwest_error_to_io_error)?;
191 let response: LoginResponse = serde_json::from_reader(bytes.reader())?;
192 let auth_data = AuthData {
193 username: username.to_owned(),
194 access_token: response.access_token.clone(),
195 };
196 *(self.auth_data.borrow_mut()) = Some(auth_data);
197 Ok(response)
198 }
199 _ => Err(http_response_to_io_error(response).await),
200 }
201 }
202
203 async fn logout(&mut self) -> io::Result<()> {
204 let mut auth_data = self.auth_data.borrow_mut();
205 let response = {
206 let auth_data = Self::require_auth_data(auth_data.as_ref())?;
207 self.client
208 .post(self.make_url(&format!("api/users/{}/logout", auth_data.username)))
209 .headers(self.default_headers())
210 .header("Content-Length", 0)
211 .bearer_auth(auth_data.access_token.as_str())
212 .send()
213 .await
214 .map_err(reqwest_error_to_io_error)?
215 };
216 match response.status() {
217 StatusCode::OK => {
218 *auth_data = None;
219 Ok(())
220 }
221 _ => Err(http_response_to_io_error(response).await),
222 }
223 }
224
225 fn is_logged_in(&self) -> bool {
226 self.auth_data.borrow().is_some()
227 }
228
229 fn logged_in_username(&self) -> Option<String> {
230 self.auth_data.borrow().as_ref().map(|x| x.username.to_owned())
231 }
232
233 async fn get_files(&mut self, username: &str) -> io::Result<GetFilesResponse> {
234 let mut builder = self
235 .client
236 .get(self.make_url(&format!("api/users/{}/files", username)))
237 .headers(self.default_headers());
238 if let Some(auth_data) = self.auth_data.borrow().as_ref() {
239 builder = builder.bearer_auth(auth_data.access_token.as_str());
240 }
241 let response = builder.send().await.map_err(reqwest_error_to_io_error)?;
242 match response.status() {
243 StatusCode::OK => {
244 let bytes = response.bytes().await.map_err(reqwest_error_to_io_error)?;
245 let response: GetFilesResponse = serde_json::from_reader(bytes.reader())?;
246 Ok(response)
247 }
248 _ => Err(http_response_to_io_error(response).await),
249 }
250 }
251
252 async fn get_file(&mut self, username: &str, filename: &str) -> io::Result<Vec<u8>> {
253 let mut builder = self
254 .client
255 .get(self.make_url(&format!("api/users/{}/files/{}", username, filename)))
256 .headers(self.default_headers());
257 if let Some(auth_data) = self.auth_data.borrow().as_ref() {
258 builder = builder.bearer_auth(auth_data.access_token.as_str());
259 }
260 let response = builder.send().await.map_err(reqwest_error_to_io_error)?;
261 match response.status() {
262 StatusCode::OK => {
263 Ok(response.bytes().await.map_err(reqwest_error_to_io_error)?.to_vec())
264 }
265 _ => Err(http_response_to_io_error(response).await),
266 }
267 }
268
269 async fn get_file_acls(&mut self, username: &str, filename: &str) -> io::Result<FileAcls> {
270 let mut headers = self.default_headers();
271 headers.insert("X-EndBASIC-GetContent", "false".parse().unwrap());
272 headers.insert("X-EndBASIC-GetReaders", "true".parse().unwrap());
273 let mut builder = self
274 .client
275 .get(self.make_url(&format!("api/users/{}/files/{}", username, filename)))
276 .headers(headers);
277 if let Some(auth_data) = self.auth_data.borrow().as_ref() {
278 builder = builder.bearer_auth(auth_data.access_token.as_str());
279 }
280 let response = builder.send().await.map_err(reqwest_error_to_io_error)?;
281 match response.status() {
282 StatusCode::OK => {
283 let mut readers = vec![];
284 for h in response.headers().get_all("X-EndBASIC-Reader") {
285 match h.to_str() {
286 Ok(value) => readers.push(value.to_owned()),
287 Err(e) => {
288 return Err(io::Error::new(
289 io::ErrorKind::InvalidData,
290 format!("Server returned invalid reader ACL: {}", e),
291 ));
292 }
293 }
294 }
295
296 let bytes = response.bytes().await.map_err(reqwest_error_to_io_error)?;
297 debug_assert!(bytes.is_empty(), "Did not expect server to return content");
298
299 Ok(FileAcls::default().with_readers(readers))
300 }
301 _ => Err(http_response_to_io_error(response).await),
302 }
303 }
304
305 async fn patch_file_content(
306 &mut self,
307 username: &str,
308 filename: &str,
309 content: Vec<u8>,
310 ) -> io::Result<()> {
311 let auth_data = self.auth_data.borrow();
312
313 let response = self
314 .client
315 .patch(self.make_url(&format!("api/users/{}/files/{}", username, filename)))
316 .headers(self.default_headers())
317 .header("Content-Type", "application/octet-stream")
318 .header("X-EndBASIC-PatchContent", "true")
319 .body(content)
320 .bearer_auth(Self::require_auth_data(auth_data.as_ref())?.access_token.as_str())
321 .send()
322 .await
323 .map_err(reqwest_error_to_io_error)?;
324 match response.status() {
325 StatusCode::OK | StatusCode::CREATED => Ok(()),
326 _ => Err(http_response_to_io_error(response).await),
327 }
328 }
329
330 async fn patch_file_acls(
331 &mut self,
332 username: &str,
333 filename: &str,
334 add: &FileAcls,
335 remove: &FileAcls,
336 ) -> io::Result<()> {
337 let auth_data = self.auth_data.borrow();
338
339 let mut builder = self
340 .client
341 .patch(self.make_url(&format!("api/users/{}/files/{}", username, filename)))
342 .headers(self.default_headers())
343 .header("Content-Type", "application/octet-stream")
344 .header("X-EndBASIC-PatchContent", "false");
346
347 for reader in add.readers() {
348 builder = builder.header("X-EndBASIC-AddReader", reader);
349 }
350 for reader in remove.readers() {
351 builder = builder.header("X-EndBASIC-RemoveReader", reader);
352 }
353
354 let response = builder
355 .bearer_auth(Self::require_auth_data(auth_data.as_ref())?.access_token.as_str())
356 .send()
357 .await
358 .map_err(reqwest_error_to_io_error)?;
359 match response.status() {
360 StatusCode::OK | StatusCode::CREATED => Ok(()),
361 _ => Err(http_response_to_io_error(response).await),
362 }
363 }
364
365 async fn delete_file(&mut self, username: &str, filename: &str) -> io::Result<()> {
366 let auth_data = self.auth_data.borrow();
367
368 let response = self
369 .client
370 .delete(self.make_url(&format!("api/users/{}/files/{}", username, filename)))
371 .headers(self.default_headers())
372 .header("Content-Length", 0)
373 .bearer_auth(Self::require_auth_data(auth_data.as_ref())?.access_token.as_str())
374 .send()
375 .await
376 .map_err(reqwest_error_to_io_error)?;
377 match response.status() {
378 StatusCode::OK => Ok(()),
379 _ => Err(http_response_to_io_error(response).await),
380 }
381 }
382}
383
384#[cfg(test)]
385mod testutils {
386 use super::*;
387 use std::collections::HashMap;
388 use std::env;
389
390 pub struct AutoDeletingService<S: Service> {
392 service: S,
394
395 current_user: Option<(String, String)>,
397
398 files_to_delete: HashMap<String, (String, String)>,
400 }
401
402 impl<S: Service> AutoDeletingService<S> {
403 pub fn new(service: S) -> Self {
405 Self { service, current_user: None, files_to_delete: HashMap::default() }
406 }
407 }
408
409 #[async_trait(?Send)]
410 impl<S: Service> Service for AutoDeletingService<S> {
411 async fn signup(&mut self, request: &SignupRequest) -> io::Result<()> {
412 self.service.signup(request).await
413 }
414
415 async fn login(&mut self, username: &str, password: &str) -> io::Result<LoginResponse> {
416 let result = self.service.login(username, password).await;
417 if result.is_ok() {
418 self.current_user = Some((username.to_owned(), password.to_owned()));
419 }
420 result
421 }
422
423 async fn logout(&mut self) -> io::Result<()> {
424 let result = self.service.logout().await;
425 if result.is_ok() {
426 self.current_user = None;
427 }
428 result
429 }
430
431 fn is_logged_in(&self) -> bool {
432 self.service.is_logged_in()
433 }
434
435 fn logged_in_username(&self) -> Option<String> {
436 self.service.logged_in_username()
437 }
438
439 async fn get_files(&mut self, username: &str) -> io::Result<GetFilesResponse> {
440 self.service.get_files(username).await
441 }
442
443 async fn get_file(&mut self, username: &str, filename: &str) -> io::Result<Vec<u8>> {
444 self.service.get_file(username, filename).await
445 }
446
447 async fn get_file_acls(&mut self, username: &str, filename: &str) -> io::Result<FileAcls> {
448 self.service.get_file_acls(username, filename).await
449 }
450
451 async fn patch_file_content(
452 &mut self,
453 username: &str,
454 filename: &str,
455 content: Vec<u8>,
456 ) -> io::Result<()> {
457 let result = self.service.patch_file_content(username, filename, content).await;
458 if result.is_ok() {
459 self.files_to_delete
460 .insert(filename.to_owned(), self.current_user.clone().unwrap());
461 }
462 result
463 }
464
465 async fn patch_file_acls(
466 &mut self,
467 username: &str,
468 filename: &str,
469 add: &FileAcls,
470 remove: &FileAcls,
471 ) -> io::Result<()> {
472 self.service.patch_file_acls(username, filename, add, remove).await
473 }
474
475 async fn delete_file(&mut self, username: &str, filename: &str) -> io::Result<()> {
476 let result = self.service.delete_file(username, filename).await;
477 if result.is_ok() {
478 self.files_to_delete.remove(filename);
479 }
480 result
481 }
482 }
483
484 impl<S: Service> Drop for AutoDeletingService<S> {
485 fn drop(&mut self) {
486 #[tokio::main]
487 #[allow(clippy::single_match)]
488 async fn cleanup<S: Service>(service: &mut AutoDeletingService<S>) {
489 if let Some((username, _password)) = service.current_user.as_ref() {
490 service
491 .service
492 .logout()
493 .await
494 .map_err(|e| {
495 format!("Failed to log out for {} during cleanup: {}", username, e)
496 })
497 .unwrap();
498 }
499
500 for (filename, (username, password)) in service.files_to_delete.iter() {
501 service
502 .service
503 .login(username, password)
504 .await
505 .map_err(|e| {
506 format!("Failed to log in for {} during cleanup: {}", username, e)
507 })
508 .unwrap();
509
510 service
511 .service
512 .delete_file(username, filename)
513 .await
514 .map_err(|e| {
515 format!("Failed to delete file {} during cleanup: {}", filename, e)
516 })
517 .unwrap();
518
519 service
520 .service
521 .logout()
522 .await
523 .map_err(|e| {
524 format!("Failed to log out for {} during cleanup: {}", username, e)
525 })
526 .unwrap();
527 }
528 }
529 cleanup(self);
530 }
531 }
532
533 pub(crate) fn new_service_from_env() -> AutoDeletingService<CloudService> {
535 let service_api = env::var("SERVICE_URL").expect("Expected env config not found");
536 AutoDeletingService::new(CloudService::new(&service_api).unwrap())
537 }
538
539 pub(crate) struct TestContext {
541 pub(super) service: AutoDeletingService<CloudService>,
542 }
543
544 impl TestContext {
545 pub(crate) fn new_from_env() -> Self {
547 TestContext { service: new_service_from_env() }
548 }
549
550 pub(crate) fn get_username(&self, i: u8) -> String {
552 env::var(format!("TEST_ACCOUNT_{}_USERNAME", i)).expect("Expected env config not found")
553 }
554
555 pub(crate) async fn do_login(&mut self, i: u8) -> String {
561 let username = self.get_username(i);
562 let password = env::var(format!("TEST_ACCOUNT_{}_PASSWORD", i))
563 .expect("Expected env config not found");
564 let _response = self.service.login(&username, &password).await.unwrap();
565 username
566 }
567
568 pub(crate) async fn do_logout(&mut self) {
570 self.service.logout().await.unwrap();
571 }
572
573 pub(crate) fn random_file(&mut self) -> (String, Vec<u8>) {
576 let filename = format!("file-{}", rand::random::<u64>());
577 let content = format!("Test content for {}", filename);
578 (filename, content.into_bytes())
579 }
580 }
581}
582
583#[cfg(test)]
584mod tests {
585 use super::testutils::*;
594 use super::*;
595 use std::env;
596
597 #[test]
598 #[ignore = "Requires environment configuration and is expensive"]
599 fn test_login_ok() {
600 #[tokio::main]
601 async fn run(context: &mut TestContext) {
602 let _username = context.do_login(1).await;
603 }
604 run(&mut TestContext::new_from_env());
605 }
606
607 #[test]
608 #[ignore = "Requires environment configuration and is expensive"]
609 fn test_login_bad_password() {
610 #[tokio::main]
611 async fn run(context: &mut TestContext) {
612 let username =
613 env::var("TEST_ACCOUNT_1_USERNAME").expect("Expected env config not found");
614 let password = "this is an invalid password for the test account";
615
616 let err = context.service.login(&username, password).await.unwrap_err();
617 assert_eq!(io::ErrorKind::PermissionDenied, err.kind());
618 }
619 run(&mut TestContext::new_from_env());
620 }
621
622 #[test]
623 #[ignore = "Requires environment configuration and is expensive"]
624 fn test_get_files() {
625 #[tokio::main]
626 async fn run(context: &mut TestContext) {
627 let username = context.do_login(1).await;
628
629 let mut needed_bytes = 0;
630 let mut needed_files = 0;
631 let mut filenames_and_contents = vec![];
632 for _ in 0..5 {
633 let (filename, content) = context.random_file();
634
635 needed_bytes += content.len() as u64;
636 needed_files += 1;
637 filenames_and_contents.push((filename, content));
638 }
639
640 let response = context.service.get_files(&username).await.unwrap();
641 for (filename, _content) in &filenames_and_contents {
642 assert!(!response.files.iter().any(|x| &x.filename == filename));
643 }
644 let disk_quota: DiskSpace = response.disk_quota.unwrap().into();
645 let disk_free: DiskSpace = response.disk_free.unwrap().into();
646 assert!(disk_quota.bytes() > 0);
647 assert!(disk_quota.files() > 0);
648 assert!(disk_free.bytes() >= needed_bytes, "Not enough space for test run");
649 assert!(disk_free.files() >= needed_files, "Not enough space for test run");
650
651 for (filename, _content) in &filenames_and_contents {
652 let err = context.service.get_file(&username, filename).await.unwrap_err();
653 assert_eq!(io::ErrorKind::NotFound, err.kind(), "{}", err);
654 }
655
656 for (filename, content) in &filenames_and_contents {
657 context
658 .service
659 .patch_file_content(&username, filename, content.clone())
660 .await
661 .unwrap();
662 }
663
664 let response = context.service.get_files(&username).await.unwrap();
665 for (filename, _content) in &filenames_and_contents {
666 assert!(response.files.iter().any(|x| &x.filename == filename));
667 }
668 }
669 run(&mut TestContext::new_from_env());
670 }
671
672 async fn do_get_and_patch_file_test<B: Into<Vec<u8>>>(
673 context: &mut TestContext,
674 filename: &str,
675 content: B,
676 ) {
677 let username = context.do_login(1).await;
678
679 let content = content.into();
680 context.service.patch_file_content(&username, filename, content.clone()).await.unwrap();
681 assert_eq!(content, context.service.get_file(&username, filename).await.unwrap());
682 }
683
684 #[test]
685 #[ignore = "Requires environment configuration and is expensive"]
686 fn test_get_and_patch_file_ok() {
687 #[tokio::main]
688 async fn run(context: &mut TestContext) {
689 let (filename, content) = context.random_file();
690 do_get_and_patch_file_test(context, &filename, content).await;
691 }
692 run(&mut TestContext::new_from_env());
693 }
694
695 #[test]
696 #[ignore = "Requires environment configuration and is expensive"]
697 fn test_get_and_patch_file_empty_ok() {
698 #[tokio::main]
699 async fn run(context: &mut TestContext) {
700 let (filename, _content) = context.random_file();
701 do_get_and_patch_file_test(context, &filename, &[]).await;
702 }
703 run(&mut TestContext::new_from_env());
704 }
705
706 #[test]
707 #[ignore = "Requires environment configuration and is expensive"]
708 fn test_get_and_patch_file_utf8() {
709 #[tokio::main]
710 async fn run(context: &mut TestContext) {
711 let (filename, _content) = context.random_file();
712 let content = "안녕하세요";
713 do_get_and_patch_file_test(context, &filename, content).await;
714 }
715 run(&mut TestContext::new_from_env());
716 }
717
718 #[test]
719 #[ignore = "Requires environment configuration and is expensive"]
720 fn test_get_file_not_found() {
721 #[tokio::main]
722 async fn run(context: &mut TestContext) {
723 let username = context.do_login(1).await;
724 let (filename, _content) = context.random_file();
725
726 let err = context.service.get_file(&username, &filename).await.unwrap_err();
727 assert_eq!(io::ErrorKind::NotFound, err.kind(), "{}", err);
728 }
729 run(&mut TestContext::new_from_env());
730 }
731
732 #[test]
733 #[ignore = "Requires environment configuration and is expensive"]
734 fn test_patch_file_without_login() {
735 #[tokio::main]
736 async fn run(context: &mut TestContext) {
737 let username = context.get_username(1);
738
739 context.do_login(1).await;
740 let (filename, _content) = context.random_file();
741
742 context.do_logout().await;
743 let err = context
744 .service
745 .patch_file_content(&username, &filename, b"foo".to_vec())
746 .await
747 .unwrap_err();
748 assert_eq!(io::ErrorKind::PermissionDenied, err.kind(), "{}", err);
749 assert!(format!("{}", err).contains("Not logged in"));
750 }
751 run(&mut TestContext::new_from_env());
752 }
753
754 #[test]
755 #[ignore = "Requires environment configuration and is expensive"]
756 fn test_acls_private() {
757 #[tokio::main]
758 async fn run(context: &mut TestContext) {
759 let (filename, content) = context.random_file();
760
761 let username1 = context.get_username(1);
762 let username2 = context.get_username(2);
763
764 context.do_login(1).await;
766 context
767 .service
768 .patch_file_content(&username1, &filename, content.clone())
769 .await
770 .unwrap();
771
772 context.do_login(2).await;
774 let err = context.service.get_file(&username1, &filename).await.unwrap_err();
775 assert_eq!(io::ErrorKind::NotFound, err.kind(), "{}", err);
776
777 context.do_login(1).await;
779 context
780 .service
781 .patch_file_acls(
782 &username1,
783 &filename,
784 &FileAcls::default().with_readers([username2]),
785 &FileAcls::default(),
786 )
787 .await
788 .unwrap();
789
790 context.do_login(2).await;
792 let response = context.service.get_file(&username1, &filename).await.unwrap();
793 assert_eq!(content, response);
794 }
795 run(&mut TestContext::new_from_env());
796 }
797
798 #[test]
799 #[ignore = "Requires environment configuration and is expensive"]
800 fn test_acls_public() {
801 #[tokio::main]
802 async fn run(context: &mut TestContext) {
803 let (filename, content) = context.random_file();
804
805 let username1 = context.get_username(1);
806
807 context.do_login(1).await;
809 context
810 .service
811 .patch_file_content(&username1, &filename, content.clone())
812 .await
813 .unwrap();
814
815 context.do_logout().await;
817 let err = context.service.get_file(&username1, &filename).await.unwrap_err();
818 assert_eq!(io::ErrorKind::NotFound, err.kind(), "{}", err);
819
820 context.do_login(1).await;
822 context
823 .service
824 .patch_file_acls(
825 &username1,
826 &filename,
827 &FileAcls::default().with_readers(["public".to_owned()]),
828 &FileAcls::default(),
829 )
830 .await
831 .unwrap();
832
833 context.do_logout().await;
835 let response = context.service.get_file(&username1, &filename).await.unwrap();
836 assert_eq!(content, response);
837 }
838 run(&mut TestContext::new_from_env());
839 }
840
841 #[test]
842 #[ignore = "Requires environment configuration and is expensive"]
843 fn test_delete_file_ok() {
844 #[tokio::main]
845 async fn run(context: &mut TestContext) {
846 let username = context.do_login(1).await;
847 let (filename, content) = context.random_file();
848
849 context.service.patch_file_content(&username, &filename, content).await.unwrap();
850
851 context.service.delete_file(&username, &filename).await.unwrap();
852
853 let err = context.service.get_file(&username, &filename).await.unwrap_err();
854 assert_eq!(io::ErrorKind::NotFound, err.kind(), "{}", err);
855 assert!(format!("{}", err).contains("(server code: 404"));
856 }
857 run(&mut TestContext::new_from_env());
858 }
859
860 #[test]
861 #[ignore = "Requires environment configuration and is expensive"]
862 fn test_delete_file_not_found() {
863 #[tokio::main]
864 async fn run(context: &mut TestContext) {
865 let username = context.do_login(1).await;
866 let (filename, _content) = context.random_file();
867
868 let err = context.service.delete_file(&username, &filename).await.unwrap_err();
869 assert_eq!(io::ErrorKind::NotFound, err.kind(), "{}", err);
870 assert!(format!("{}", err).contains("(server code: 404"));
871 }
872 run(&mut TestContext::new_from_env());
873 }
874
875 #[test]
876 #[ignore = "Requires environment configuration and is expensive"]
877 fn test_delete_file_without_login() {
878 #[tokio::main]
879 async fn run(context: &mut TestContext) {
880 let username = context.get_username(1);
881
882 context.do_login(1).await;
883 let (filename, _content) = context.random_file();
884
885 context.do_logout().await;
886 let err = context.service.delete_file(&username, &filename).await.unwrap_err();
887 assert_eq!(io::ErrorKind::PermissionDenied, err.kind(), "{}", err);
888 assert!(format!("{}", err).contains("Not logged in"));
889 }
890 run(&mut TestContext::new_from_env());
891 }
892}