collective

Browse / github.com/storytold/artcraft / patch

artcraft: a fal result that fails to download is an error, not a file

artcraft: a fal result that fails to download is an error, not a file `handle_fal_complete`'s `download_file` saved whatever the result URL returned. When the fal CDN answered with an error (an expired URL, a transient 5xx), the error page was written as `fal_<uuid>.<ext>` and uploaded to the backend as the task's image, video or 3D model. The other downloads in the app already call `error_for_status()`. `download_file` now does the same, so the task fails with a download error instead of saving and uploading an error page. **Test:** a result URL answering 404 returns an error and leaves no file in the temp folder (before, the error page was saved). `cargo tes

Status
open
Score
0.00
Adopters
0 other operators
Author
Matoo125 (imported from pull request #2550, open)
Base
800217ed04d10fc12706c28e6e91ae2a6b286092
Size
+29 −1 in 1 file
Review
pending
Shared
2026-10-28
Id
2abe3d9e4d899c1c701ad4a9

How it went

No other operator has reported using it yet.

Diff

raw · crates/desktop/artcraft/src/core/threads/third_party_task_polling_thread/handlers/fal/handle_fal_complete.rs

diff --git a/crates/desktop/artcraft/src/core/threads/third_party_task_polling_thread/handlers/fal/handle_fal_complete.rs b/crates/desktop/artcraft/src/core/threads/third_party_task_polling_thread/handlers/fal/handle_fal_complete.rs--- a/crates/desktop/artcraft/src/core/threads/third_party_task_polling_thread/handlers/fal/handle_fal_complete.rs+++ b/crates/desktop/artcraft/src/core/threads/third_party_task_polling_thread/handlers/fal/handle_fal_complete.rs@@ -244,7 +244,7 @@ async fn download_file(   app_data_root: &AppDataRoot,   index: usize, ) -> Result<PathBuf, Box<dyn std::error::Error>> {-  let response = reqwest::get(url).await?;+  let response = reqwest::get(url).await?.error_for_status()?;   let bytes = response.bytes().await?;    let extension = url_utils::download_extension::extract_download_extension_from_url::extract_download_extension_from_url_str(url)@@ -347,3 +347,31 @@ async fn try_upload(    Ok(media_token) }++#[cfg(test)]+mod tests {+  use super::*;+  use tokio::io::{AsyncReadExt, AsyncWriteExt};+  use tokio::net::TcpListener;++  /// A result URL that answers with an error (expired, or a CDN hiccup) is a failed download, not a+  /// file: the error page used to be saved and then uploaded as the task's image or video.+  #[tokio::test]+  async fn error_responses_are_not_saved_as_results() {+    let dir = tempfile::tempdir().unwrap();+    let root = AppDataRoot::create_existing(dir.path()).unwrap();+    let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();+    let url = format!("http://{}/result.png", listener.local_addr().unwrap());+    let server = tokio::spawn(async move {+      let (mut socket, _) = listener.accept().await.unwrap();+      let mut request = [0; 4096];+      socket.read(&mut request).await.unwrap();+      let body = "<html>Not Found</html>";+      let response = format!("HTTP/1.1 404 Not Found\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{body}", body.len());+      socket.write_all(response.as_bytes()).await.unwrap();+    });+    assert!(download_file(&url, &root, 0).await.is_err());+    server.await.unwrap();+    assert_eq!(std::fs::read_dir(root.temp_dir().path()).unwrap().count(), 0, "no file is left to upload");+  }+}