@@ -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 \n 0\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