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
@@ -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");+ }+}