Skip to content

Commit ce69008

Browse files
committed
Add test with updated resource and etag
Signed-off-by: Marcel Guzik <[email protected]>
1 parent 01701c9 commit ce69008

File tree

1 file changed

+106
-0
lines changed

1 file changed

+106
-0
lines changed

crates/common/download/src/download/tests/partial_response.rs

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,3 +102,109 @@ async fn resume_download_when_disconnected() {
102102

103103
server_task.abort();
104104
}
105+
106+
/// If after retrying ETag of the resource is different, we should download it
107+
/// from scratch again.
108+
#[tokio::test]
109+
async fn resume_download_with_etag_changed() {
110+
let file_v1 = "AAAABBBBCCCCDDDD";
111+
let file_v2 = "XXXXYYYYZZZZWWWW";
112+
let chunk_size = 4;
113+
114+
let listener = TcpListener::bind("localhost:0").await.unwrap();
115+
let port = listener.local_addr().unwrap().port();
116+
117+
// server should do 3 things:
118+
// - only send a portion of the first request
119+
// - after the first request update the resource
120+
// - serve 2nd request normally (but we expect it to be a range request)
121+
let server_task = tokio::spawn(async move {
122+
let mut request_count = 0;
123+
while let Ok((mut stream, _addr)) = listener.accept().await {
124+
let response_task = async move {
125+
let (reader, mut writer) = stream.split();
126+
let mut lines = BufReader::new(reader).lines();
127+
let mut range: Option<std::ops::Range<usize>> = None;
128+
129+
// We got an HTTP request, read the lines of the request
130+
while let Ok(Some(line)) = lines.next_line().await {
131+
if line.to_ascii_lowercase().contains("range:") {
132+
let (_, bytes) = line.split_once('=').unwrap();
133+
let (start, end) = bytes.split_once('-').unwrap();
134+
let start = start.parse().unwrap_or(0);
135+
let end = end.parse().unwrap_or(file_v2.len());
136+
range = Some(start..end)
137+
}
138+
// On `\r\n\r\n` (empty line) stop reading the request
139+
// and start responding
140+
if line.is_empty() {
141+
break;
142+
}
143+
}
144+
145+
let file = if request_count == 0 { file_v1 } else { file_v2 };
146+
let etag = if request_count == 0 {
147+
"v1-initial"
148+
} else {
149+
"v2-changed"
150+
};
151+
152+
if let Some(range) = range {
153+
// Return range for both first and subsequent requests
154+
let start = range.start;
155+
let end = range.end.min(file.len());
156+
let header = format!(
157+
"HTTP/1.1 206 Partial Content\r\n\
158+
transfer-encoding: chunked\r\n\
159+
connection: close\r\n\
160+
content-type: application/octet-stream\r\n\
161+
content-range: bytes {start}-{end}/*\r\n\
162+
accept-ranges: bytes\r\n\
163+
etag: \"{etag}\"\r\n"
164+
);
165+
let body = &file[start..end];
166+
let size = body.len();
167+
let msg = format!("{header}\r\n{size:x}\r\n{body}\r\n0\r\n\r\n");
168+
writer.write_all(msg.as_bytes()).await.unwrap();
169+
writer.flush().await.unwrap();
170+
} else {
171+
let header = format!(
172+
"HTTP/1.1 200 OK\r\n\
173+
transfer-encoding: chunked\r\n\
174+
connection: close\r\n\
175+
content-type: application/octet-stream\r\n\
176+
accept-ranges: bytes\r\n\
177+
etag: \"{etag}\"\r\n"
178+
);
179+
180+
let body = &file[0..chunk_size];
181+
let size = body.len();
182+
let msg = format!("{header}\r\n{size:x}\r\n{body}\r\n");
183+
writer.write_all(msg.as_bytes()).await.unwrap();
184+
writer.flush().await.unwrap();
185+
// Connection drops here without sending final chunk
186+
}
187+
};
188+
request_count += 1;
189+
tokio::spawn(response_task);
190+
}
191+
});
192+
193+
// Wait until task binds a listener on the TCP port
194+
tokio::time::sleep(std::time::Duration::from_millis(50)).await;
195+
196+
let tmpdir = TempDir::new().unwrap();
197+
let target_path = tmpdir.path().join("partial_download_etag");
198+
199+
let downloader = Downloader::new(target_path, None, CloudHttpConfig::test_value());
200+
let url = DownloadInfo::new(&format!("http://localhost:{port}/"));
201+
202+
downloader.download(&url).await.unwrap();
203+
let saved_file = std::fs::read_to_string(downloader.filename()).unwrap();
204+
// Should have the complete new file content since ETag changed
205+
assert_eq!(saved_file, file_v2);
206+
207+
downloader.cleanup().await.unwrap();
208+
209+
server_task.abort();
210+
}

0 commit comments

Comments
 (0)