Skip to content

Commit 1bf0087

Browse files
authoredMar 4, 2024··
Merge pull request umccr#230 from umccr/test/concat-responses
test: concatenate byte range responses
2 parents 44fc6e3 + 9c1e7a0 commit 1bf0087

22 files changed

+683
-193
lines changed
 

‎Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎data/cram/htsnexus_test_NA12878.cram

43.6 KB
Binary file not shown.
0 Bytes
Binary file not shown.

‎htsget-actix/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ futures-util = { version = "0.3" }
2626
htsget-http = { version = "0.4.12", path = "../htsget-http", default-features = false }
2727
htsget-search = { version = "0.6.6", path = "../htsget-search", default-features = false }
2828
htsget-config = { version = "0.8.1", path = "../htsget-config", default-features = false }
29-
htsget-test = { version = "0.5.4", path = "../htsget-test", features = ["server-tests", "cors-tests"], default-features = false }
29+
htsget-test = { version = "0.5.4", path = "../htsget-test", features = ["http"], default-features = false }
3030
futures = { version = "0.3" }
3131
tokio = { version = "1.28", features = ["macros", "rt-multi-thread"] }
3232

‎htsget-actix/benches/request_benchmarks.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use serde::{Deserialize, Serialize};
1212

1313
use htsget_config::types::{Headers, JsonResponse};
1414
use htsget_http::{PostRequest, Region};
15-
use htsget_test::http_tests::{default_config_fixed_port, default_dir, default_dir_data};
15+
use htsget_test::http::{default_config_fixed_port, default_dir, default_dir_data};
1616

1717
const REFSERVER_DOCKER_IMAGE: &str = "ga4gh/htsget-refserver:1.5.0";
1818
const BENCHMARK_DURATION_SECONDS: u64 = 30;

‎htsget-actix/src/lib.rs

+18-22
Original file line numberDiff line numberDiff line change
@@ -151,12 +151,12 @@ mod tests {
151151

152152
use htsget_config::types::JsonResponse;
153153
use htsget_search::storage::data_server::BindDataServer;
154-
use htsget_test::http_tests::{config_with_tls, default_test_config};
155-
use htsget_test::http_tests::{
154+
use htsget_test::http::server::expected_url_path;
155+
use htsget_test::http::{config_with_tls, default_test_config};
156+
use htsget_test::http::{cors, server};
157+
use htsget_test::http::{
156158
Header as TestHeader, Response as TestResponse, TestRequest, TestServer,
157159
};
158-
use htsget_test::server_tests::expected_url_path;
159-
use htsget_test::{cors_tests, server_tests};
160160

161161
use crate::Config;
162162

@@ -272,55 +272,51 @@ mod tests {
272272

273273
#[actix_web::test]
274274
async fn get_http_tickets() {
275-
server_tests::test_get::<JsonResponse, _>(&ActixTestServer::default()).await;
275+
server::test_get::<JsonResponse, _>(&ActixTestServer::default()).await;
276276
}
277277

278278
#[actix_web::test]
279279
async fn post_http_tickets() {
280-
server_tests::test_post::<JsonResponse, _>(&ActixTestServer::default()).await;
280+
server::test_post::<JsonResponse, _>(&ActixTestServer::default()).await;
281281
}
282282

283283
#[actix_web::test]
284284
async fn parameterized_get_http_tickets() {
285-
server_tests::test_parameterized_get::<JsonResponse, _>(&ActixTestServer::default()).await;
285+
server::test_parameterized_get::<JsonResponse, _>(&ActixTestServer::default()).await;
286286
}
287287

288288
#[actix_web::test]
289289
async fn parameterized_post_http_tickets() {
290-
server_tests::test_parameterized_post::<JsonResponse, _>(&ActixTestServer::default()).await;
290+
server::test_parameterized_post::<JsonResponse, _>(&ActixTestServer::default()).await;
291291
}
292292

293293
#[actix_web::test]
294294
async fn parameterized_post_class_header_http_tickets() {
295-
server_tests::test_parameterized_post_class_header::<JsonResponse, _>(
296-
&ActixTestServer::default(),
297-
)
298-
.await;
295+
server::test_parameterized_post_class_header::<JsonResponse, _>(&ActixTestServer::default())
296+
.await;
299297
}
300298

301299
#[actix_web::test]
302300
async fn service_info() {
303-
server_tests::test_service_info(&ActixTestServer::default()).await;
301+
server::test_service_info(&ActixTestServer::default()).await;
304302
}
305303

306304
#[actix_web::test]
307305
async fn get_https_tickets() {
308306
let base_path = TempDir::new().unwrap();
309-
server_tests::test_get::<JsonResponse, _>(&ActixTestServer::new_with_tls(base_path.path()))
310-
.await;
307+
server::test_get::<JsonResponse, _>(&ActixTestServer::new_with_tls(base_path.path())).await;
311308
}
312309

313310
#[actix_web::test]
314311
async fn post_https_tickets() {
315312
let base_path = TempDir::new().unwrap();
316-
server_tests::test_post::<JsonResponse, _>(&ActixTestServer::new_with_tls(base_path.path()))
317-
.await;
313+
server::test_post::<JsonResponse, _>(&ActixTestServer::new_with_tls(base_path.path())).await;
318314
}
319315

320316
#[actix_web::test]
321317
async fn parameterized_get_https_tickets() {
322318
let base_path = TempDir::new().unwrap();
323-
server_tests::test_parameterized_get::<JsonResponse, _>(&ActixTestServer::new_with_tls(
319+
server::test_parameterized_get::<JsonResponse, _>(&ActixTestServer::new_with_tls(
324320
base_path.path(),
325321
))
326322
.await;
@@ -329,7 +325,7 @@ mod tests {
329325
#[actix_web::test]
330326
async fn parameterized_post_https_tickets() {
331327
let base_path = TempDir::new().unwrap();
332-
server_tests::test_parameterized_post::<JsonResponse, _>(&ActixTestServer::new_with_tls(
328+
server::test_parameterized_post::<JsonResponse, _>(&ActixTestServer::new_with_tls(
333329
base_path.path(),
334330
))
335331
.await;
@@ -338,19 +334,19 @@ mod tests {
338334
#[actix_web::test]
339335
async fn parameterized_post_class_header_https_tickets() {
340336
let base_path = TempDir::new().unwrap();
341-
server_tests::test_parameterized_post_class_header::<JsonResponse, _>(
337+
server::test_parameterized_post_class_header::<JsonResponse, _>(
342338
&ActixTestServer::new_with_tls(base_path.path()),
343339
)
344340
.await;
345341
}
346342

347343
#[actix_web::test]
348344
async fn cors_simple_request() {
349-
cors_tests::test_cors_simple_request(&ActixTestServer::default()).await;
345+
cors::test_cors_simple_request(&ActixTestServer::default()).await;
350346
}
351347

352348
#[actix_web::test]
353349
async fn cors_preflight_request() {
354-
cors_tests::test_cors_preflight_request(&ActixTestServer::default()).await;
350+
cors::test_cors_preflight_request(&ActixTestServer::default()).await;
355351
}
356352
}

‎htsget-lambda/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ lambda_runtime = { version = "0.8" }
2323
htsget-config = { version = "0.8.1", path = "../htsget-config", default-features = false }
2424
htsget-search = { version = "0.6.6", path = "../htsget-search", default-features = false }
2525
htsget-http = { version = "0.4.12", path = "../htsget-http", default-features = false }
26-
htsget-test = { version = "0.5.4", path = "../htsget-test", features = ["server-tests", "cors-tests"], default-features = false }
26+
htsget-test = { version = "0.5.4", path = "../htsget-test", features = ["http"], default-features = false }
2727
serde = { version = "1.0" }
2828
serde_json = "1.0"
2929
mime = "0.3"

‎htsget-lambda/src/lib.rs

+18-22
Original file line numberDiff line numberDiff line change
@@ -281,10 +281,10 @@ mod tests {
281281
use htsget_http::Endpoint;
282282
use htsget_search::storage::configure_cors;
283283
use htsget_search::storage::data_server::BindDataServer;
284-
use htsget_test::http_tests::{config_with_tls, default_test_config, get_test_file};
285-
use htsget_test::http_tests::{Header, Response as TestResponse, TestRequest, TestServer};
286-
use htsget_test::server_tests::{expected_url_path, test_response, test_response_service_info};
287-
use htsget_test::{cors_tests, server_tests};
284+
use htsget_test::http::server::{expected_url_path, test_response, test_response_service_info};
285+
use htsget_test::http::{config_with_tls, default_test_config, get_test_file};
286+
use htsget_test::http::{cors, server};
287+
use htsget_test::http::{Header, Response as TestResponse, TestRequest, TestServer};
288288

289289
use super::*;
290290

@@ -383,60 +383,56 @@ mod tests {
383383

384384
#[tokio::test]
385385
async fn get_http_tickets() {
386-
server_tests::test_get::<JsonResponse, _>(&LambdaTestServer::default()).await;
386+
server::test_get::<JsonResponse, _>(&LambdaTestServer::default()).await;
387387
}
388388

389389
#[tokio::test]
390390
async fn post_http_tickets() {
391-
server_tests::test_post::<JsonResponse, _>(&LambdaTestServer::default()).await;
391+
server::test_post::<JsonResponse, _>(&LambdaTestServer::default()).await;
392392
}
393393

394394
#[tokio::test]
395395
async fn parameterized_get_http_tickets() {
396-
server_tests::test_parameterized_get::<JsonResponse, _>(&LambdaTestServer::default()).await;
396+
server::test_parameterized_get::<JsonResponse, _>(&LambdaTestServer::default()).await;
397397
}
398398

399399
#[tokio::test]
400400
async fn parameterized_post_http_tickets() {
401-
server_tests::test_parameterized_post::<JsonResponse, _>(&LambdaTestServer::default()).await;
401+
server::test_parameterized_post::<JsonResponse, _>(&LambdaTestServer::default()).await;
402402
}
403403

404404
#[tokio::test]
405405
async fn parameterized_post_class_header_http_tickets() {
406-
server_tests::test_parameterized_post_class_header::<JsonResponse, _>(
407-
&LambdaTestServer::default(),
408-
)
409-
.await;
406+
server::test_parameterized_post_class_header::<JsonResponse, _>(&LambdaTestServer::default())
407+
.await;
410408
}
411409

412410
#[tokio::test]
413411
async fn cors_simple_request() {
414-
cors_tests::test_cors_simple_request(&LambdaTestServer::default()).await;
412+
cors::test_cors_simple_request(&LambdaTestServer::default()).await;
415413
}
416414

417415
#[tokio::test]
418416
async fn cors_preflight_request() {
419-
cors_tests::test_cors_preflight_request(&LambdaTestServer::default()).await;
417+
cors::test_cors_preflight_request(&LambdaTestServer::default()).await;
420418
}
421419

422420
#[tokio::test]
423421
async fn get_https_tickets() {
424422
let base_path = TempDir::new().unwrap();
425-
server_tests::test_get::<JsonResponse, _>(&LambdaTestServer::new_with_tls(base_path.path()))
426-
.await;
423+
server::test_get::<JsonResponse, _>(&LambdaTestServer::new_with_tls(base_path.path())).await;
427424
}
428425

429426
#[tokio::test]
430427
async fn post_https_tickets() {
431428
let base_path = TempDir::new().unwrap();
432-
server_tests::test_post::<JsonResponse, _>(&LambdaTestServer::new_with_tls(base_path.path()))
433-
.await;
429+
server::test_post::<JsonResponse, _>(&LambdaTestServer::new_with_tls(base_path.path())).await;
434430
}
435431

436432
#[tokio::test]
437433
async fn parameterized_get_https_tickets() {
438434
let base_path = TempDir::new().unwrap();
439-
server_tests::test_parameterized_get::<JsonResponse, _>(&LambdaTestServer::new_with_tls(
435+
server::test_parameterized_get::<JsonResponse, _>(&LambdaTestServer::new_with_tls(
440436
base_path.path(),
441437
))
442438
.await;
@@ -445,7 +441,7 @@ mod tests {
445441
#[tokio::test]
446442
async fn parameterized_post_https_tickets() {
447443
let base_path = TempDir::new().unwrap();
448-
server_tests::test_parameterized_post::<JsonResponse, _>(&LambdaTestServer::new_with_tls(
444+
server::test_parameterized_post::<JsonResponse, _>(&LambdaTestServer::new_with_tls(
449445
base_path.path(),
450446
))
451447
.await;
@@ -454,15 +450,15 @@ mod tests {
454450
#[tokio::test]
455451
async fn parameterized_post_class_header_https_tickets() {
456452
let base_path = TempDir::new().unwrap();
457-
server_tests::test_parameterized_post_class_header::<JsonResponse, _>(
453+
server::test_parameterized_post_class_header::<JsonResponse, _>(
458454
&LambdaTestServer::new_with_tls(base_path.path()),
459455
)
460456
.await;
461457
}
462458

463459
#[tokio::test]
464460
async fn service_info() {
465-
server_tests::test_service_info(&LambdaTestServer::default()).await;
461+
server::test_service_info(&LambdaTestServer::default()).await;
466462
}
467463

468464
#[tokio::test]

‎htsget-search/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ hyper-rustls = { version = "0.24", features = ["rustls-native-certs", "http2", "
6060
# Error control, tracing, config
6161
thiserror = "1.0"
6262
htsget-config = { version = "0.8.1", path = "../htsget-config", default-features = false }
63-
htsget-test = { version = "0.5.4", path = "../htsget-test", features = ["cors-tests"], default-features = false }
63+
htsget-test = { version = "0.5.4", path = "../htsget-test", features = ["http"], default-features = false }
6464
tracing = "0.1"
6565
base64 = "0.21"
6666
serde = "1.0"

‎htsget-search/src/htsget/bam_search.rs

+74-11
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ pub(crate) mod tests {
169169
use std::future::Future;
170170

171171
use htsget_config::storage::local::LocalStorage as ConfigLocalStorage;
172+
use htsget_test::http::concat::ConcatResponse;
172173
use htsget_test::util::expected_bgzf_eof_data_url;
173174

174175
#[cfg(feature = "s3-storage")]
@@ -181,6 +182,7 @@ pub(crate) mod tests {
181182

182183
const DATA_LOCATION: &str = "data/bam";
183184
const INDEX_FILE_LOCATION: &str = "htsnexus_test_NA12878.bam.bai";
185+
pub(crate) const BAM_FILE_NAME: &str = "htsnexus_test_NA12878.bam";
184186

185187
#[tokio::test]
186188
async fn search_all_reads() {
@@ -198,7 +200,9 @@ pub(crate) mod tests {
198200
Url::new(expected_bgzf_eof_data_url()),
199201
],
200202
));
201-
assert_eq!(response, expected_response)
203+
assert_eq!(response, expected_response);
204+
205+
Some((BAM_FILE_NAME.to_string(), (response.unwrap(), Body).into()))
202206
})
203207
.await;
204208
}
@@ -224,13 +228,39 @@ pub(crate) mod tests {
224228
Url::new(expected_bgzf_eof_data_url()).with_class(Body),
225229
],
226230
));
227-
assert_eq!(response, expected_response)
231+
assert_eq!(response, expected_response);
232+
233+
Some((BAM_FILE_NAME.to_string(), (response.unwrap(), Body).into()))
234+
})
235+
.await;
236+
}
237+
238+
#[tokio::test]
239+
async fn search_reference_name_without_seq_range_chr11() {
240+
with_local_storage(|storage| async move {
241+
let search = BamSearch::new(storage.clone());
242+
let query = Query::new_with_default_request("htsnexus_test_NA12878", Format::Bam)
243+
.with_reference_name("11");
244+
let response = search.search(query).await;
245+
println!("{response:#?}");
246+
247+
let expected_response = Ok(Response::new(
248+
Format::Bam,
249+
vec![
250+
Url::new(expected_url())
251+
.with_headers(Headers::default().with_header("Range", "bytes=0-996014")),
252+
Url::new(expected_bgzf_eof_data_url()),
253+
],
254+
));
255+
assert_eq!(response, expected_response);
256+
257+
Some((BAM_FILE_NAME.to_string(), (response.unwrap(), Body).into()))
228258
})
229259
.await;
230260
}
231261

232262
#[tokio::test]
233-
async fn search_reference_name_without_seq_range() {
263+
async fn search_reference_name_without_seq_range_chr20() {
234264
with_local_storage(|storage| async move {
235265
let search = BamSearch::new(storage.clone());
236266
let query = Query::new_with_default_request("htsnexus_test_NA12878", Format::Bam)
@@ -250,7 +280,9 @@ pub(crate) mod tests {
250280
Url::new(expected_bgzf_eof_data_url()).with_class(Body),
251281
],
252282
));
253-
assert_eq!(response, expected_response)
283+
assert_eq!(response, expected_response);
284+
285+
Some((BAM_FILE_NAME.to_string(), (response.unwrap(), Body).into()))
254286
})
255287
.await;
256288
}
@@ -284,7 +316,9 @@ pub(crate) mod tests {
284316
Url::new(expected_bgzf_eof_data_url()).with_class(Body),
285317
],
286318
));
287-
assert_eq!(response, expected_response)
319+
assert_eq!(response, expected_response);
320+
321+
Some((BAM_FILE_NAME.to_string(), (response.unwrap(), Body).into()))
288322
})
289323
.await;
290324
}
@@ -311,7 +345,9 @@ pub(crate) mod tests {
311345
Url::new(expected_bgzf_eof_data_url()).with_class(Body),
312346
],
313347
));
314-
assert_eq!(response, expected_response)
348+
assert_eq!(response, expected_response);
349+
350+
Some((BAM_FILE_NAME.to_string(), (response.unwrap(), Body).into()))
315351
})
316352
.await;
317353
}
@@ -343,7 +379,9 @@ pub(crate) mod tests {
343379
Url::new(expected_bgzf_eof_data_url()),
344380
],
345381
));
346-
assert_eq!(response, expected_response)
382+
assert_eq!(response, expected_response);
383+
384+
Some((BAM_FILE_NAME.to_string(), (response.unwrap(), Body).into()))
347385
})
348386
.await
349387
}
@@ -372,10 +410,12 @@ pub(crate) mod tests {
372410
Url::new(expected_bgzf_eof_data_url()).with_class(Body),
373411
],
374412
));
375-
assert_eq!(response, expected_response)
413+
assert_eq!(response, expected_response);
414+
415+
Some((BAM_FILE_NAME.to_string(), (response.unwrap(), Body).into()))
376416
},
377417
DATA_LOCATION,
378-
&["htsnexus_test_NA12878.bam", INDEX_FILE_LOCATION],
418+
&[BAM_FILE_NAME, INDEX_FILE_LOCATION],
379419
)
380420
.await
381421
}
@@ -395,7 +435,12 @@ pub(crate) mod tests {
395435
.with_headers(Headers::default().with_header("Range", "bytes=0-4667"))
396436
.with_class(Header)],
397437
));
398-
assert_eq!(response, expected_response)
438+
assert_eq!(response, expected_response);
439+
440+
Some((
441+
BAM_FILE_NAME.to_string(),
442+
(response.unwrap(), Header).into(),
443+
))
399444
})
400445
.await;
401446
}
@@ -419,6 +464,8 @@ pub(crate) mod tests {
419464
],
420465
));
421466
assert_eq!(response, expected_response);
467+
468+
Some((BAM_FILE_NAME.to_string(), (response.unwrap(), Body).into()))
422469
})
423470
.await;
424471
}
@@ -433,6 +480,8 @@ pub(crate) mod tests {
433480
println!("{response:#?}");
434481

435482
assert!(matches!(response, Err(NotFound(_))));
483+
484+
None
436485
})
437486
.await;
438487
}
@@ -445,6 +494,8 @@ pub(crate) mod tests {
445494
let query = Query::new_with_default_request("htsnexus_test_NA12878", Format::Bam);
446495
let response = search.search(query).await;
447496
assert!(matches!(response, Err(NotFound(_))));
497+
498+
None
448499
},
449500
DATA_LOCATION,
450501
&[INDEX_FILE_LOCATION],
@@ -461,6 +512,8 @@ pub(crate) mod tests {
461512
.with_reference_name("20");
462513
let response = search.search(query).await;
463514
assert!(matches!(response, Err(NotFound(_))));
515+
516+
None
464517
},
465518
DATA_LOCATION,
466519
&[INDEX_FILE_LOCATION],
@@ -477,6 +530,8 @@ pub(crate) mod tests {
477530
Query::new_with_default_request("htsnexus_test_NA12878", Format::Bam).with_class(Header);
478531
let response = search.search(query).await;
479532
assert!(matches!(response, Err(NotFound(_))));
533+
534+
None
480535
},
481536
DATA_LOCATION,
482537
&[INDEX_FILE_LOCATION],
@@ -496,6 +551,8 @@ pub(crate) mod tests {
496551
let response = search.get_header_end_offset(&index).await;
497552

498553
assert_eq!(response, Ok(70204));
554+
555+
None
499556
},
500557
DATA_LOCATION,
501558
&[INDEX_FILE_LOCATION],
@@ -512,6 +569,8 @@ pub(crate) mod tests {
512569
let query = Query::new_with_default_request("htsnexus_test_NA12878", Format::Bam);
513570
let response = search.search(query).await;
514571
assert!(response.is_err());
572+
573+
None
515574
},
516575
DATA_LOCATION,
517576
&[INDEX_FILE_LOCATION],
@@ -529,6 +588,8 @@ pub(crate) mod tests {
529588
.with_reference_name("20");
530589
let response = search.search(query).await;
531590
assert!(response.is_err());
591+
592+
None
532593
},
533594
DATA_LOCATION,
534595
&[INDEX_FILE_LOCATION],
@@ -546,6 +607,8 @@ pub(crate) mod tests {
546607
Query::new_with_default_request("htsnexus_test_NA12878", Format::Bam).with_class(Header);
547608
let response = search.search(query).await;
548609
assert!(response.is_err());
610+
611+
None
549612
},
550613
DATA_LOCATION,
551614
&[INDEX_FILE_LOCATION],
@@ -556,7 +619,7 @@ pub(crate) mod tests {
556619
pub(crate) async fn with_local_storage<F, Fut>(test: F)
557620
where
558621
F: FnOnce(Arc<LocalStorage<ConfigLocalStorage>>) -> Fut,
559-
Fut: Future<Output = ()>,
622+
Fut: Future<Output = Option<(String, ConcatResponse)>>,
560623
{
561624
with_local_storage_fn(test, DATA_LOCATION, &[]).await
562625
}

‎htsget-search/src/htsget/bcf_search.rs

+54-7
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,8 @@ mod tests {
132132
use std::future::Future;
133133

134134
use htsget_config::storage::local::LocalStorage as ConfigLocalStorage;
135+
use htsget_config::types::Class::Body;
136+
use htsget_test::http::concat::ConcatResponse;
135137
use htsget_test::util::expected_bgzf_eof_data_url;
136138

137139
#[cfg(feature = "s3-storage")]
@@ -145,6 +147,8 @@ mod tests {
145147

146148
const DATA_LOCATION: &str = "data/bcf";
147149
const INDEX_FILE_LOCATION: &str = "vcf-spec-v4.3.bcf.csi";
150+
const BCF_FILE_NAME_SPEC: &str = "vcf-spec-v4.3.bcf";
151+
const BCF_FILE_NAME_SAMPLE: &str = "sample1-bcbio-cancer.bcf";
148152

149153
#[tokio::test]
150154
async fn search_all_variants() {
@@ -156,7 +160,12 @@ mod tests {
156160
println!("{response:#?}");
157161

158162
let expected_response = Ok(expected_bcf_response(filename));
159-
assert_eq!(response, expected_response)
163+
assert_eq!(response, expected_response);
164+
165+
Some((
166+
BCF_FILE_NAME_SAMPLE.to_string(),
167+
(response.unwrap(), Body).into(),
168+
))
160169
})
161170
.await
162171
}
@@ -178,7 +187,12 @@ mod tests {
178187
Url::new(expected_bgzf_eof_data_url()),
179188
],
180189
));
181-
assert_eq!(response, expected_response)
190+
assert_eq!(response, expected_response);
191+
192+
Some((
193+
BCF_FILE_NAME_SPEC.to_string(),
194+
(response.unwrap(), Body).into(),
195+
))
182196
})
183197
.await
184198
}
@@ -203,7 +217,12 @@ mod tests {
203217
println!("{response:#?}");
204218

205219
let expected_response = Ok(expected_bcf_response(filename));
206-
assert_eq!(response, expected_response)
220+
assert_eq!(response, expected_response);
221+
222+
Some((
223+
BCF_FILE_NAME_SAMPLE.to_string(),
224+
(response.unwrap(), Body).into(),
225+
))
207226
})
208227
.await
209228
}
@@ -233,7 +252,12 @@ mod tests {
233252
.with_headers(Headers::default().with_header("Range", "bytes=0-949"))
234253
.with_class(Header)],
235254
));
236-
assert_eq!(response, expected_response)
255+
assert_eq!(response, expected_response);
256+
257+
Some((
258+
BCF_FILE_NAME_SPEC.to_string(),
259+
(response.unwrap(), Header).into(),
260+
))
237261
})
238262
.await
239263
}
@@ -246,6 +270,8 @@ mod tests {
246270
let query = Query::new_with_default_request("vcf-spec-v4.3", Format::Bcf);
247271
let response = search.search(query).await;
248272
assert!(matches!(response, Err(NotFound(_))));
273+
274+
None
249275
},
250276
DATA_LOCATION,
251277
&[INDEX_FILE_LOCATION],
@@ -262,6 +288,8 @@ mod tests {
262288
Query::new_with_default_request("vcf-spec-v4.3", Format::Bcf).with_reference_name("chrM");
263289
let response = search.search(query).await;
264290
assert!(matches!(response, Err(NotFound(_))));
291+
292+
None
265293
},
266294
DATA_LOCATION,
267295
&[INDEX_FILE_LOCATION],
@@ -278,6 +306,8 @@ mod tests {
278306
Query::new_with_default_request("vcf-spec-v4.3", Format::Bcf).with_class(Header);
279307
let response = search.search(query).await;
280308
assert!(matches!(response, Err(NotFound(_))));
309+
310+
None
281311
},
282312
DATA_LOCATION,
283313
&[INDEX_FILE_LOCATION],
@@ -295,6 +325,8 @@ mod tests {
295325
println!("{response:#?}");
296326

297327
assert!(matches!(response, Err(NotFound(_))));
328+
329+
None
298330
})
299331
.await;
300332
}
@@ -311,6 +343,8 @@ mod tests {
311343
let response = search.get_header_end_offset(&index).await;
312344

313345
assert_eq!(response, Ok(65536));
346+
347+
None
314348
},
315349
DATA_LOCATION,
316350
&[INDEX_FILE_LOCATION],
@@ -327,6 +361,8 @@ mod tests {
327361
let query = Query::new_with_default_request("vcf-spec-v4.3", Format::Bcf);
328362
let response = search.search(query).await;
329363
assert!(response.is_err());
364+
365+
None
330366
},
331367
DATA_LOCATION,
332368
&[INDEX_FILE_LOCATION],
@@ -344,6 +380,8 @@ mod tests {
344380
Query::new_with_default_request("vcf-spec-v4.3", Format::Bcf).with_reference_name("chrM");
345381
let response = search.search(query).await;
346382
assert!(response.is_err());
383+
384+
None
347385
},
348386
DATA_LOCATION,
349387
&[INDEX_FILE_LOCATION],
@@ -361,14 +399,18 @@ mod tests {
361399
Query::new_with_default_request("vcf-spec-v4.3", Format::Bcf).with_class(Header);
362400
let response = search.search(query).await;
363401
assert!(response.is_err());
402+
403+
None
364404
},
365405
DATA_LOCATION,
366406
&[INDEX_FILE_LOCATION],
367407
)
368408
.await
369409
}
370410

371-
async fn test_reference_sequence_with_seq_range(storage: Arc<LocalStorage<ConfigLocalStorage>>) {
411+
async fn test_reference_sequence_with_seq_range(
412+
storage: Arc<LocalStorage<ConfigLocalStorage>>,
413+
) -> Option<(String, ConcatResponse)> {
372414
let search = BcfSearch::new(storage.clone());
373415
let filename = "sample1-bcbio-cancer";
374416
let query = Query::new_with_default_request(filename, Format::Bcf)
@@ -379,7 +421,12 @@ mod tests {
379421
println!("{response:#?}");
380422

381423
let expected_response = Ok(expected_bcf_response(filename));
382-
assert_eq!(response, expected_response)
424+
assert_eq!(response, expected_response);
425+
426+
Some((
427+
BCF_FILE_NAME_SAMPLE.to_string(),
428+
(response.unwrap(), Body).into(),
429+
))
383430
}
384431

385432
fn expected_bcf_response(filename: &str) -> Response {
@@ -396,7 +443,7 @@ mod tests {
396443
async fn with_local_storage<F, Fut>(test: F)
397444
where
398445
F: FnOnce(Arc<LocalStorage<ConfigLocalStorage>>) -> Fut,
399-
Fut: Future<Output = ()>,
446+
Fut: Future<Output = Option<(String, ConcatResponse)>>,
400447
{
401448
with_local_storage_fn(test, "data/bcf", &[]).await
402449
}

‎htsget-search/src/htsget/cram_search.rs

+72-17
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,7 @@ mod tests {
292292
use std::future::Future;
293293

294294
use htsget_config::storage::local::LocalStorage as ConfigLocalStorage;
295+
use htsget_test::http::concat::ConcatResponse;
295296
use htsget_test::util::expected_cram_eof_data_url;
296297

297298
#[cfg(feature = "s3-storage")]
@@ -304,6 +305,7 @@ mod tests {
304305

305306
const DATA_LOCATION: &str = "data/cram";
306307
const INDEX_FILE_LOCATION: &str = "htsnexus_test_NA12878.cram.crai";
308+
const CRAM_FILE_NAME: &str = "htsnexus_test_NA12878.cram";
307309

308310
#[tokio::test]
309311
async fn search_all_reads() {
@@ -317,11 +319,13 @@ mod tests {
317319
Format::Cram,
318320
vec![
319321
Url::new(expected_url())
320-
.with_headers(Headers::default().with_header("Range", "bytes=0-1627755")),
322+
.with_headers(Headers::default().with_header("Range", "bytes=0-1672409")),
321323
Url::new(expected_cram_eof_data_url()),
322324
],
323325
));
324-
assert_eq!(response, expected_response)
326+
assert_eq!(response, expected_response);
327+
328+
Some((CRAM_FILE_NAME.to_string(), (response.unwrap(), Body).into()))
325329
})
326330
.await;
327331
}
@@ -339,21 +343,47 @@ mod tests {
339343
Format::Cram,
340344
vec![
341345
Url::new(expected_url())
342-
.with_headers(Headers::default().with_header("Range", "bytes=0-6086"))
346+
.with_headers(Headers::default().with_header("Range", "bytes=0-6133"))
343347
.with_class(Header),
344348
Url::new(expected_url())
345-
.with_headers(Headers::default().with_header("Range", "bytes=1280106-1627755"))
349+
.with_headers(Headers::default().with_header("Range", "bytes=1324614-1672409"))
346350
.with_class(Body),
347351
Url::new(expected_cram_eof_data_url()).with_class(Body),
348352
],
349353
));
350-
assert_eq!(response, expected_response)
354+
assert_eq!(response, expected_response);
355+
356+
Some((CRAM_FILE_NAME.to_string(), (response.unwrap(), Body).into()))
351357
})
352358
.await;
353359
}
354360

355361
#[tokio::test]
356-
async fn search_reference_name_without_seq_range() {
362+
async fn search_reference_name_without_seq_range_chr11() {
363+
with_local_storage(|storage| async move {
364+
let search = CramSearch::new(storage.clone());
365+
let query = Query::new_with_default_request("htsnexus_test_NA12878", Format::Cram)
366+
.with_reference_name("11");
367+
let response = search.search(query).await;
368+
println!("{response:#?}");
369+
370+
let expected_response = Ok(Response::new(
371+
Format::Cram,
372+
vec![
373+
Url::new(expected_url())
374+
.with_headers(Headers::default().with_header("Range", "bytes=0-625727")),
375+
Url::new(expected_cram_eof_data_url()),
376+
],
377+
));
378+
assert_eq!(response, expected_response);
379+
380+
Some((CRAM_FILE_NAME.to_string(), (response.unwrap(), Body).into()))
381+
})
382+
.await;
383+
}
384+
385+
#[tokio::test]
386+
async fn search_reference_name_without_seq_range_chr20() {
357387
with_local_storage(|storage| async move {
358388
let search = CramSearch::new(storage.clone());
359389
let query = Query::new_with_default_request("htsnexus_test_NA12878", Format::Cram)
@@ -365,15 +395,17 @@ mod tests {
365395
Format::Cram,
366396
vec![
367397
Url::new(expected_url())
368-
.with_headers(Headers::default().with_header("Range", "bytes=0-6086"))
398+
.with_headers(Headers::default().with_header("Range", "bytes=0-6133"))
369399
.with_class(Header),
370400
Url::new(expected_url())
371-
.with_headers(Headers::default().with_header("Range", "bytes=604231-1280105"))
401+
.with_headers(Headers::default().with_header("Range", "bytes=625728-1324613"))
372402
.with_class(Body),
373403
Url::new(expected_cram_eof_data_url()).with_class(Body),
374404
],
375405
));
376-
assert_eq!(response, expected_response)
406+
assert_eq!(response, expected_response);
407+
408+
Some((CRAM_FILE_NAME.to_string(), (response.unwrap(), Body).into()))
377409
})
378410
.await;
379411
}
@@ -393,11 +425,13 @@ mod tests {
393425
Format::Cram,
394426
vec![
395427
Url::new(expected_url())
396-
.with_headers(Headers::default().with_header("Range", "bytes=0-465708")),
428+
.with_headers(Headers::default().with_header("Range", "bytes=0-480537")),
397429
Url::new(expected_cram_eof_data_url()),
398430
],
399431
));
400-
assert_eq!(response, expected_response)
432+
assert_eq!(response, expected_response);
433+
434+
Some((CRAM_FILE_NAME.to_string(), (response.unwrap(), Body).into()))
401435
})
402436
.await;
403437
}
@@ -414,7 +448,9 @@ mod tests {
414448
println!("{response:#?}");
415449

416450
let expected_response = Ok(expected_response_with_start());
417-
assert_eq!(response, expected_response)
451+
assert_eq!(response, expected_response);
452+
453+
Some((CRAM_FILE_NAME.to_string(), (response.unwrap(), Body).into()))
418454
})
419455
.await;
420456
}
@@ -430,7 +466,9 @@ mod tests {
430466
println!("{response:#?}");
431467

432468
let expected_response = Ok(expected_response_with_start());
433-
assert_eq!(response, expected_response)
469+
assert_eq!(response, expected_response);
470+
471+
Some((CRAM_FILE_NAME.to_string(), (response.unwrap(), Body).into()))
434472
})
435473
.await;
436474
}
@@ -440,7 +478,7 @@ mod tests {
440478
Format::Cram,
441479
vec![
442480
Url::new(expected_url())
443-
.with_headers(Headers::default().with_header("Range", "bytes=0-604230")),
481+
.with_headers(Headers::default().with_header("Range", "bytes=0-625727")),
444482
Url::new(expected_cram_eof_data_url()),
445483
],
446484
)
@@ -458,10 +496,15 @@ mod tests {
458496
let expected_response = Ok(Response::new(
459497
Format::Cram,
460498
vec![Url::new(expected_url())
461-
.with_headers(Headers::default().with_header("Range", "bytes=0-6086"))
499+
.with_headers(Headers::default().with_header("Range", "bytes=0-6133"))
462500
.with_class(Header)],
463501
));
464-
assert_eq!(response, expected_response)
502+
assert_eq!(response, expected_response);
503+
504+
Some((
505+
CRAM_FILE_NAME.to_string(),
506+
(response.unwrap(), Header).into(),
507+
))
465508
})
466509
.await;
467510
}
@@ -474,6 +517,8 @@ mod tests {
474517
let query = Query::new_with_default_request("htsnexus_test_NA12878", Format::Cram);
475518
let response = search.search(query).await;
476519
assert!(matches!(response, Err(NotFound(_))));
520+
521+
None
477522
},
478523
DATA_LOCATION,
479524
&[INDEX_FILE_LOCATION],
@@ -490,6 +535,8 @@ mod tests {
490535
.with_reference_name("20");
491536
let response = search.search(query).await;
492537
assert!(matches!(response, Err(NotFound(_))));
538+
539+
None
493540
},
494541
DATA_LOCATION,
495542
&[INDEX_FILE_LOCATION],
@@ -506,6 +553,8 @@ mod tests {
506553
Query::new_with_default_request("htsnexus_test_NA12878", Format::Cram).with_class(Header);
507554
let response = search.search(query).await;
508555
assert!(matches!(response, Err(NotFound(_))));
556+
557+
None
509558
},
510559
DATA_LOCATION,
511560
&[INDEX_FILE_LOCATION],
@@ -522,6 +571,8 @@ mod tests {
522571
let query = Query::new_with_default_request("htsnexus_test_NA12878", Format::Cram);
523572
let response = search.search(query).await;
524573
assert!(response.is_err());
574+
575+
None
525576
},
526577
DATA_LOCATION,
527578
&[INDEX_FILE_LOCATION],
@@ -539,6 +590,8 @@ mod tests {
539590
.with_reference_name("20");
540591
let response = search.search(query).await;
541592
assert!(response.is_err());
593+
594+
None
542595
},
543596
DATA_LOCATION,
544597
&[INDEX_FILE_LOCATION],
@@ -556,6 +609,8 @@ mod tests {
556609
Query::new_with_default_request("htsnexus_test_NA12878", Format::Cram).with_class(Header);
557610
let response = search.search(query).await;
558611
assert!(response.is_err());
612+
613+
None
559614
},
560615
DATA_LOCATION,
561616
&[INDEX_FILE_LOCATION],
@@ -566,7 +621,7 @@ mod tests {
566621
async fn with_local_storage<F, Fut>(test: F)
567622
where
568623
F: FnOnce(Arc<LocalStorage<ConfigLocalStorage>>) -> Fut,
569-
Fut: Future<Output = ()>,
624+
Fut: Future<Output = Option<(String, ConcatResponse)>>,
570625
{
571626
with_local_storage_fn(test, "data/cram", &[]).await
572627
}

‎htsget-search/src/htsget/from_storage.rs

+59-17
Original file line numberDiff line numberDiff line change
@@ -133,14 +133,17 @@ pub(crate) mod tests {
133133
use tempfile::TempDir;
134134

135135
use htsget_config::storage;
136+
use htsget_config::types::Class::Body;
136137
use htsget_config::types::Scheme::Http;
138+
use htsget_test::http::concat::ConcatResponse;
137139
use htsget_test::util::expected_bgzf_eof_data_url;
138140

139141
use crate::htsget::bam_search::tests::{
140-
expected_url as bam_expected_url, with_local_storage as with_bam_local_storage,
142+
expected_url as bam_expected_url, with_local_storage as with_bam_local_storage, BAM_FILE_NAME,
141143
};
142144
use crate::htsget::vcf_search::tests::{
143145
expected_url as vcf_expected_url, with_local_storage as with_vcf_local_storage,
146+
VCF_FILE_NAME_SPEC,
144147
};
145148
#[cfg(feature = "s3-storage")]
146149
use crate::storage::s3::tests::with_aws_s3_storage_fn;
@@ -164,7 +167,9 @@ pub(crate) mod tests {
164167
Url::new(expected_bgzf_eof_data_url()),
165168
],
166169
));
167-
assert_eq!(response, expected_response)
170+
assert_eq!(response, expected_response);
171+
172+
Some((BAM_FILE_NAME.to_string(), (response.unwrap(), Body).into()))
168173
})
169174
.await;
170175
}
@@ -179,6 +184,11 @@ pub(crate) mod tests {
179184
println!("{response:#?}");
180185

181186
assert_eq!(response, expected_vcf_response(filename));
187+
188+
Some((
189+
VCF_FILE_NAME_SPEC.to_string(),
190+
(response.unwrap(), Body).into(),
191+
))
182192
})
183193
.await;
184194
}
@@ -192,6 +202,11 @@ pub(crate) mod tests {
192202
let response = HtsGetFromStorage::<()>::from_local(&local_storage, &query).await;
193203

194204
assert_eq!(response, expected_vcf_response(filename));
205+
206+
Some((
207+
VCF_FILE_NAME_SPEC.to_string(),
208+
(response.unwrap(), Body).into(),
209+
))
195210
},
196211
"data/vcf",
197212
&[],
@@ -216,6 +231,11 @@ pub(crate) mod tests {
216231
let response = resolvers.search(query).await;
217232

218233
assert_eq!(response, expected_vcf_response(filename));
234+
235+
Some((
236+
VCF_FILE_NAME_SPEC.to_string(),
237+
(response.unwrap(), Body).into(),
238+
))
219239
},
220240
"data/vcf",
221241
&[],
@@ -234,33 +254,33 @@ pub(crate) mod tests {
234254
))
235255
}
236256

237-
async fn copy_files(from_path: &str, to_path: &Path, file_names: &[&str]) -> PathBuf {
257+
async fn copy_files_from(from_path: &str, to_path: &Path, copy_files: &[&str]) -> PathBuf {
238258
let mut base_path = std::env::current_dir()
239259
.unwrap()
240260
.parent()
241261
.unwrap()
242262
.join(from_path);
243263

244-
for file_name in file_names {
264+
for file_name in copy_files {
245265
fs::copy(base_path.join(file_name), to_path.join(file_name)).unwrap();
246266
}
247-
if !file_names.is_empty() {
267+
if !copy_files.is_empty() {
248268
base_path = PathBuf::from(to_path);
249269
}
250270

251271
base_path
252272
}
253273

254-
async fn with_config_local_storage<F, Fut>(test: F, path: &str, file_names: &[&str])
274+
async fn with_config_local_storage<F, Fut>(test: F, path: &str, copy_files: &[&str])
255275
where
256276
F: FnOnce(PathBuf, LocalStorageConfig) -> Fut,
257-
Fut: Future<Output = ()>,
277+
Fut: Future<Output = Option<(String, ConcatResponse)>>,
258278
{
259279
let tmp_dir = TempDir::new().unwrap();
260-
let base_path = copy_files(path, tmp_dir.path(), file_names).await;
280+
let base_path = copy_files_from(path, tmp_dir.path(), copy_files).await;
261281

262282
println!("{:#?}", base_path);
263-
test(
283+
let response = test(
264284
base_path.clone(),
265285
LocalStorageConfig::new(
266286
Http,
@@ -269,13 +289,27 @@ pub(crate) mod tests {
269289
"/data".to_string(),
270290
),
271291
)
272-
.await
292+
.await;
293+
294+
read_records(response, &base_path).await;
273295
}
274296

275-
pub(crate) async fn with_local_storage_fn<F, Fut>(test: F, path: &str, file_names: &[&str])
297+
async fn read_records(response: Option<(String, ConcatResponse)>, base_path: &Path) {
298+
if let Some((target_file, response)) = response {
299+
response
300+
.concat_from_file_path(&base_path.join(target_file))
301+
.await
302+
.unwrap()
303+
.read_records()
304+
.await
305+
.unwrap();
306+
}
307+
}
308+
309+
pub(crate) async fn with_local_storage_fn<F, Fut>(test: F, path: &str, copy_files: &[&str])
276310
where
277311
F: FnOnce(Arc<LocalStorage<LocalStorageConfig>>) -> Fut,
278-
Fut: Future<Output = ()>,
312+
Fut: Future<Output = Option<(String, ConcatResponse)>>,
279313
{
280314
with_config_local_storage(
281315
|base_path, local_storage| async {
@@ -285,23 +319,31 @@ pub(crate) mod tests {
285319
.await
286320
},
287321
path,
288-
file_names,
322+
copy_files,
289323
)
290324
.await;
291325
}
292326

293327
#[cfg(feature = "s3-storage")]
294-
pub(crate) async fn with_aws_storage_fn<F, Fut>(test: F, path: &str, file_names: &[&str])
328+
pub(crate) async fn with_aws_storage_fn<F, Fut>(test: F, path: &str, copy_files: &[&str])
295329
where
296330
F: FnOnce(Arc<S3Storage>) -> Fut,
297-
Fut: Future<Output = ()>,
331+
Fut: Future<Output = Option<(String, ConcatResponse)>>,
298332
{
299333
let tmp_dir = TempDir::new().unwrap();
300334
let to_path = tmp_dir.into_path().join("folder");
301335
create_dir(&to_path).unwrap();
302336

303-
let base_path = copy_files(path, &to_path, file_names).await;
337+
let base_path = copy_files_from(path, &to_path, copy_files).await;
304338

305-
with_aws_s3_storage_fn(test, "folder".to_string(), base_path.parent().unwrap()).await;
339+
with_aws_s3_storage_fn(
340+
|storage| async {
341+
let response = test(storage).await;
342+
read_records(response, &base_path).await;
343+
},
344+
"folder".to_string(),
345+
base_path.parent().unwrap(),
346+
)
347+
.await;
306348
}
307349
}

‎htsget-search/src/htsget/vcf_search.rs

+52-5
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,8 @@ pub(crate) mod tests {
137137
use std::future::Future;
138138

139139
use htsget_config::storage::local::LocalStorage as ConfigLocalStorage;
140+
use htsget_config::types::Class::Body;
141+
use htsget_test::http::concat::ConcatResponse;
140142
use htsget_test::util::expected_bgzf_eof_data_url;
141143

142144
#[cfg(feature = "s3-storage")]
@@ -150,6 +152,8 @@ pub(crate) mod tests {
150152

151153
const VCF_LOCATION: &str = "data/vcf";
152154
const INDEX_FILE_LOCATION: &str = "spec-v4.3.vcf.gz.tbi";
155+
pub(crate) const VCF_FILE_NAME_SPEC: &str = "spec-v4.3.vcf.gz";
156+
const VCF_FILE_NAME_SAMPLE: &str = "sample1-bcbio-cancer.vcf.gz";
153157

154158
#[tokio::test]
155159
async fn search_all_variants() {
@@ -161,7 +165,12 @@ pub(crate) mod tests {
161165
println!("{response:#?}");
162166

163167
let expected_response = Ok(expected_vcf_response(filename));
164-
assert_eq!(response, expected_response)
168+
assert_eq!(response, expected_response);
169+
170+
Some((
171+
VCF_FILE_NAME_SAMPLE.to_string(),
172+
(response.unwrap(), Body).into(),
173+
))
165174
})
166175
.await;
167176
}
@@ -183,7 +192,12 @@ pub(crate) mod tests {
183192
Url::new(expected_bgzf_eof_data_url()),
184193
],
185194
));
186-
assert_eq!(response, expected_response)
195+
assert_eq!(response, expected_response);
196+
197+
Some((
198+
VCF_FILE_NAME_SPEC.to_string(),
199+
(response.unwrap(), Body).into(),
200+
))
187201
})
188202
.await;
189203
}
@@ -208,6 +222,11 @@ pub(crate) mod tests {
208222

209223
let expected_response = Ok(expected_vcf_response(filename));
210224
assert_eq!(response, expected_response);
225+
226+
Some((
227+
VCF_FILE_NAME_SAMPLE.to_string(),
228+
(response.unwrap(), Body).into(),
229+
))
211230
})
212231
.await;
213232
}
@@ -240,7 +259,12 @@ pub(crate) mod tests {
240259
.with_headers(Headers::default().with_header("Range", "bytes=0-822"))
241260
.with_class(Header)],
242261
));
243-
assert_eq!(response, expected_response)
262+
assert_eq!(response, expected_response);
263+
264+
Some((
265+
VCF_FILE_NAME_SPEC.to_string(),
266+
(response.unwrap(), Header).into(),
267+
))
244268
})
245269
.await;
246270
}
@@ -253,6 +277,8 @@ pub(crate) mod tests {
253277
let query = Query::new_with_default_request("spec-v4.3", Format::Vcf);
254278
let response = search.search(query).await;
255279
assert!(matches!(response, Err(NotFound(_))));
280+
281+
None
256282
},
257283
VCF_LOCATION,
258284
&[INDEX_FILE_LOCATION],
@@ -269,6 +295,8 @@ pub(crate) mod tests {
269295
Query::new_with_default_request("spec-v4.3", Format::Vcf).with_reference_name("chrM");
270296
let response = search.search(query).await;
271297
assert!(matches!(response, Err(NotFound(_))));
298+
299+
None
272300
},
273301
VCF_LOCATION,
274302
&[INDEX_FILE_LOCATION],
@@ -284,6 +312,8 @@ pub(crate) mod tests {
284312
let query = Query::new_with_default_request("spec-v4.3", Format::Vcf).with_class(Header);
285313
let response = search.search(query).await;
286314
assert!(matches!(response, Err(NotFound(_))));
315+
316+
None
287317
},
288318
VCF_LOCATION,
289319
&[INDEX_FILE_LOCATION],
@@ -301,6 +331,8 @@ pub(crate) mod tests {
301331
println!("{response:#?}");
302332

303333
assert!(matches!(response, Err(NotFound(_))));
334+
335+
None
304336
})
305337
.await;
306338
}
@@ -316,6 +348,8 @@ pub(crate) mod tests {
316348
let response = search.get_header_end_offset(&index).await;
317349

318350
assert_eq!(response, Ok(65536));
351+
352+
None
319353
},
320354
VCF_LOCATION,
321355
&[INDEX_FILE_LOCATION],
@@ -332,6 +366,8 @@ pub(crate) mod tests {
332366
let query = Query::new_with_default_request("spec-v4.3", Format::Vcf);
333367
let response = search.search(query).await;
334368
assert!(response.is_err());
369+
370+
None
335371
},
336372
VCF_LOCATION,
337373
&[INDEX_FILE_LOCATION],
@@ -349,6 +385,8 @@ pub(crate) mod tests {
349385
Query::new_with_default_request("spec-v4.3", Format::Vcf).with_reference_name("chrM");
350386
let response = search.search(query).await;
351387
assert!(response.is_err());
388+
389+
None
352390
},
353391
VCF_LOCATION,
354392
&[INDEX_FILE_LOCATION],
@@ -365,14 +403,18 @@ pub(crate) mod tests {
365403
let query = Query::new_with_default_request("spec-v4.3", Format::Vcf).with_class(Header);
366404
let response = search.search(query).await;
367405
assert!(response.is_err());
406+
407+
None
368408
},
369409
VCF_LOCATION,
370410
&[INDEX_FILE_LOCATION],
371411
)
372412
.await
373413
}
374414

375-
async fn test_reference_name_with_seq_range(storage: Arc<LocalStorage<ConfigLocalStorage>>) {
415+
async fn test_reference_name_with_seq_range(
416+
storage: Arc<LocalStorage<ConfigLocalStorage>>,
417+
) -> Option<(String, ConcatResponse)> {
376418
let search = VcfSearch::new(storage.clone());
377419
let filename = "sample1-bcbio-cancer";
378420
let query = Query::new_with_default_request(filename, Format::Vcf)
@@ -384,6 +426,11 @@ pub(crate) mod tests {
384426

385427
let expected_response = Ok(expected_vcf_response(filename));
386428
assert_eq!(response, expected_response);
429+
430+
Some((
431+
VCF_FILE_NAME_SAMPLE.to_string(),
432+
(response.unwrap(), Body).into(),
433+
))
387434
}
388435

389436
fn expected_vcf_response(filename: &str) -> Response {
@@ -400,7 +447,7 @@ pub(crate) mod tests {
400447
pub(crate) async fn with_local_storage<F, Fut>(test: F)
401448
where
402449
F: FnOnce(Arc<LocalStorage<ConfigLocalStorage>>) -> Fut,
403-
Fut: Future<Output = ()>,
450+
Fut: Future<Output = Option<(String, ConcatResponse)>>,
404451
{
405452
with_local_storage_fn(test, "data/vcf", &[]).await
406453
}

‎htsget-search/src/storage/data_server.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -211,8 +211,8 @@ mod tests {
211211
use reqwest::{Client, ClientBuilder, RequestBuilder};
212212
use tempfile::tempdir;
213213

214-
use htsget_test::cors_tests::{test_cors_preflight_request_uri, test_cors_simple_request_uri};
215-
use htsget_test::http_tests::{
214+
use htsget_test::http::cors::{test_cors_preflight_request_uri, test_cors_simple_request_uri};
215+
use htsget_test::http::{
216216
config_with_tls, default_cors_config, default_test_config, Header, Response as TestResponse,
217217
TestRequest, TestServer,
218218
};

‎htsget-test/Cargo.toml

+4-8
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,12 @@ homepage = "https://github.com/umccr/htsget-rs/blob/main/htsget-test/README.md"
1111
repository = "https://github.com/umccr/htsget-rs"
1212

1313
[features]
14-
http-tests = [
14+
http = [
1515
"dep:async-trait",
1616
"dep:http",
1717
"dep:serde_json",
1818
"dep:serde",
1919
"dep:htsget-config",
20-
]
21-
cors-tests = ["http-tests", "dep:htsget-config"]
22-
server-tests = [
23-
"http-tests",
24-
"dep:htsget-config",
2520
"dep:noodles",
2621
"dep:reqwest",
2722
"dep:tokio",
@@ -47,10 +42,10 @@ default = []
4742
# Server tests dependencies
4843
htsget-config = { version = "0.8.1", path = "../htsget-config", default-features = false, optional = true }
4944

50-
noodles = { version = "0.65", optional = true, features = ["async", "bgzf", "vcf"] }
45+
noodles = { version = "0.65", optional = true, features = ["async", "bgzf", "vcf", "cram", "bcf", "bam", "fasta"] }
5146

5247
reqwest = { version = "0.11", default-features = false, features = ["json", "rustls-tls"], optional = true }
53-
tokio = { version = "1", features = ["rt-multi-thread"], optional = true }
48+
tokio = { version = "1", features = ["rt-multi-thread", "fs"], optional = true }
5449
futures = { version = "0.3", optional = true }
5550
async-trait = { version = "0.1", optional = true }
5651
http = { version = "0.2", optional = true }
@@ -69,3 +64,4 @@ s3s-aws = { version = "0.8", optional = true }
6964

7065
# Default dependencies
7166
rcgen = "0.12"
67+
thiserror = "1.0"

‎htsget-test/src/error.rs

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
//! Errors defined by the htsget-test crate.
2+
//!
3+
4+
use std::fmt::Display;
5+
use std::io::Error;
6+
use std::{io, result};
7+
use thiserror::Error;
8+
9+
/// Result type for this crate.
10+
pub type Result<T> = result::Result<T, TestError>;
11+
12+
/// The error that this crate can make.
13+
#[derive(Error, Debug)]
14+
pub enum TestError {
15+
#[error("{0}")]
16+
Io(io::Error),
17+
#[error("reading records: {0}")]
18+
ReadRecord(String),
19+
#[error("concatenating response: {0}")]
20+
ConcatResponse(String),
21+
}
22+
23+
impl TestError {
24+
/// Create a read record error.
25+
pub fn read_record<E: Display>(error: E) -> Self {
26+
Self::ReadRecord(error.to_string())
27+
}
28+
29+
/// Create a concat response error.
30+
pub fn concat_response<E: Display>(error: E) -> Self {
31+
Self::ConcatResponse(error.to_string())
32+
}
33+
}
34+
35+
impl From<io::Error> for TestError {
36+
fn from(error: Error) -> Self {
37+
Self::Io(error)
38+
}
39+
}
40+
41+
impl From<TestError> for io::Error {
42+
fn from(error: TestError) -> Self {
43+
Error::new(io::ErrorKind::Other, error)
44+
}
45+
}

‎htsget-test/src/http/concat.rs

+258
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
use crate::error::TestError::ConcatResponse as ConcatResponseError;
2+
use crate::error::{Result, TestError};
3+
use base64::engine::general_purpose;
4+
use base64::Engine;
5+
use futures::future::join_all;
6+
use futures::{Stream, TryStreamExt};
7+
use htsget_config::types::{Class, Format, Response, Url};
8+
use http::{HeaderMap, HeaderName, HeaderValue};
9+
use noodles::{bam, bcf, bgzf, cram, fasta, vcf};
10+
use reqwest::Client;
11+
use std::future::Future;
12+
use std::io;
13+
use std::path::Path;
14+
use std::str::FromStr;
15+
use tokio::fs::File;
16+
use tokio::io::AsyncReadExt;
17+
18+
/// A response concatenator which concatenates url tickets.
19+
#[derive(Debug)]
20+
pub struct ConcatResponse {
21+
response: Response,
22+
class: Class,
23+
}
24+
25+
impl ConcatResponse {
26+
/// Create a new response concatenator.
27+
pub fn new(response: Response, class: Class) -> Self {
28+
Self { response, class }
29+
}
30+
31+
/// Get the inner response.
32+
pub fn into_inner(self) -> Response {
33+
self.response
34+
}
35+
36+
/// Get the inner response.
37+
pub fn response(&self) -> &Response {
38+
&self.response
39+
}
40+
41+
/// Concatenate a response into the bytes represented by the url ticket with a file path
42+
pub async fn concat_from_file_path(self, path: impl AsRef<Path>) -> Result<ReadRecords> {
43+
let file = File::open(path).await?;
44+
self.concat_from_file(file).await
45+
}
46+
47+
/// Concatenate a response into the bytes represented by the url ticket with file data.
48+
pub async fn concat_from_file(self, mut file: File) -> Result<ReadRecords> {
49+
let mut bytes = vec![];
50+
file.read_to_end(&mut bytes).await?;
51+
52+
self.concat_from_bytes(bytes.as_slice()).await
53+
}
54+
55+
/// Concatentate a response into bytes using a reqwest client.
56+
pub async fn concat_from_client(self, client: &Client) -> Result<ReadRecords> {
57+
let merged_bytes = join_all(self.response.urls.into_iter().map(|url| {
58+
Self::url_to_bytes(url, |url| async move {
59+
Ok(
60+
client
61+
.get(url.url.as_str())
62+
.headers(HeaderMap::from_iter(
63+
url
64+
.headers
65+
.unwrap_or_default()
66+
.into_inner()
67+
.into_iter()
68+
.map(|(key, value)| {
69+
Ok((
70+
HeaderName::from_str(&key).map_err(TestError::concat_response)?,
71+
HeaderValue::from_str(&value).map_err(TestError::concat_response)?,
72+
))
73+
})
74+
.collect::<Result<Vec<(HeaderName, HeaderValue)>>>()?
75+
.into_iter(),
76+
))
77+
.send()
78+
.await
79+
.map_err(TestError::concat_response)?
80+
.bytes()
81+
.await
82+
.map_err(TestError::concat_response)?
83+
.to_vec(),
84+
)
85+
})
86+
}))
87+
.await
88+
.into_iter()
89+
.collect::<Result<Vec<Vec<u8>>>>()?
90+
.concat();
91+
92+
Ok(ReadRecords::new(
93+
self.response.format,
94+
self.class,
95+
merged_bytes,
96+
))
97+
}
98+
99+
/// Concatenate a response into the bytes represented by the url ticket with bytes data.
100+
pub async fn concat_from_bytes(self, bytes: &[u8]) -> Result<ReadRecords> {
101+
let merged_bytes = join_all(self.response.urls.into_iter().map(|url| {
102+
Self::url_to_bytes(url, |url| async move {
103+
let headers = url
104+
.headers
105+
.ok_or_else(|| ConcatResponseError("missing url headers".to_string()))?
106+
.into_inner();
107+
let range = headers
108+
.get("Range")
109+
.ok_or_else(|| ConcatResponseError("missing header range".to_string()))?;
110+
let range = range
111+
.strip_prefix("bytes=")
112+
.ok_or_else(|| ConcatResponseError("failed to parse header range bytes".to_string()))?;
113+
114+
let split: Vec<&str> = range.splitn(2, '-').collect();
115+
116+
Ok(
117+
bytes[split[0].parse().map_err(TestError::read_record)?
118+
..split[1].parse::<usize>().map_err(TestError::read_record)? + 1]
119+
.to_vec(),
120+
)
121+
})
122+
}))
123+
.await
124+
.into_iter()
125+
.collect::<Result<Vec<Vec<u8>>>>()?
126+
.concat();
127+
128+
Ok(ReadRecords::new(
129+
self.response.format,
130+
self.class,
131+
merged_bytes,
132+
))
133+
}
134+
135+
/// Convert the url to bytes with a transform function for the range urls.
136+
pub async fn url_to_bytes<F, Fut>(url: Url, for_range_url: F) -> Result<Vec<u8>>
137+
where
138+
F: FnOnce(Url) -> Fut,
139+
Fut: Future<Output = Result<Vec<u8>>>,
140+
{
141+
if let Some(data_uri) = url.url.strip_prefix("data:;base64,") {
142+
general_purpose::STANDARD
143+
.decode(data_uri)
144+
.map_err(TestError::concat_response)
145+
} else {
146+
for_range_url(url).await
147+
}
148+
}
149+
}
150+
151+
impl From<(Response, Class)> for ConcatResponse {
152+
fn from((response, class): (Response, Class)) -> Self {
153+
Self::new(response, class)
154+
}
155+
}
156+
157+
/// A record reader.
158+
#[derive(Debug)]
159+
pub struct ReadRecords {
160+
format: Format,
161+
class: Class,
162+
merged_bytes: Vec<u8>,
163+
}
164+
165+
impl ReadRecords {
166+
/// Create a new record reader.
167+
pub fn new(format: Format, class: Class, merged_bytes: Vec<u8>) -> Self {
168+
Self {
169+
format,
170+
class,
171+
merged_bytes,
172+
}
173+
}
174+
175+
/// Get the format.
176+
pub fn format(&self) -> &Format {
177+
&self.format
178+
}
179+
180+
/// Get the format.
181+
pub fn merged_bytes(&self) -> &[u8] {
182+
self.merged_bytes.as_slice()
183+
}
184+
185+
/// Read records to confirm they are valid.
186+
pub async fn read_records(self) -> Result<()> {
187+
match self.format {
188+
Format::Bam => {
189+
let mut reader = bam::AsyncReader::new(self.merged_bytes.as_slice());
190+
let header = reader.read_header().await.map_err(TestError::read_record)?;
191+
println!("{:#?}", header);
192+
193+
self.iterate_records(reader.records()).await
194+
}
195+
Format::Cram => {
196+
let mut reader = cram::AsyncReader::new(self.merged_bytes.as_slice());
197+
198+
reader
199+
.read_file_definition()
200+
.await
201+
.map_err(TestError::read_record)?;
202+
let repository = fasta::Repository::default();
203+
let header = reader
204+
.read_file_header()
205+
.await
206+
.map_err(TestError::read_record)?
207+
.parse()
208+
.map_err(TestError::read_record)?;
209+
println!("{:#?}", header);
210+
211+
self
212+
.iterate_records(reader.records(&repository, &header))
213+
.await
214+
}
215+
Format::Vcf => {
216+
let mut reader =
217+
vcf::AsyncReader::new(bgzf::AsyncReader::new(self.merged_bytes.as_slice()));
218+
let header = reader.read_header().await.map_err(TestError::read_record)?;
219+
println!("{header}");
220+
221+
self.iterate_records(reader.records(&header)).await
222+
}
223+
Format::Bcf => {
224+
let mut reader = bcf::AsyncReader::new(self.merged_bytes.as_slice());
225+
reader
226+
.read_file_format()
227+
.await
228+
.map_err(TestError::read_record)?;
229+
reader.read_header().await.map_err(TestError::read_record)?;
230+
231+
self.iterate_records(reader.lazy_records()).await
232+
}
233+
}
234+
}
235+
236+
async fn iterate_records<T>(
237+
&self,
238+
mut records: impl Stream<Item = io::Result<T>> + Unpin,
239+
) -> Result<()> {
240+
if let Class::Body = self.class {
241+
let mut total_records = 0;
242+
243+
while records
244+
.try_next()
245+
.await
246+
.map_err(TestError::read_record)?
247+
.is_some()
248+
{
249+
total_records += 1;
250+
continue;
251+
}
252+
253+
println!("total records read: {}", total_records);
254+
}
255+
256+
Ok(())
257+
}
258+
}

‎htsget-test/src/cors_tests.rs ‎htsget-test/src/http/cors.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use http::header::{
44
};
55
use http::Method;
66

7-
use crate::http_tests::{Header, TestRequest, TestServer};
7+
use crate::http::{Header, TestRequest, TestServer};
88

99
/// A simple cors request test.
1010
pub async fn test_cors_simple_request<T: TestRequest>(tester: &impl TestServer<T>) {

‎htsget-test/src/http_tests.rs ‎htsget-test/src/http/mod.rs

+7
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
//! Testing functionality related to http and url tickets.
2+
//!
3+
4+
pub mod concat;
5+
pub mod cors;
6+
pub mod server;
7+
18
use std::fs;
29
use std::net::{SocketAddr, TcpListener};
310
use std::path::{Path, PathBuf};

‎htsget-test/src/server_tests.rs ‎htsget-test/src/http/server.rs

+10-66
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,16 @@
11
use std::fmt::Debug;
22
use std::net::SocketAddr;
3-
use std::str::FromStr;
43

5-
use base64::engine::general_purpose;
6-
use base64::Engine;
7-
use futures::future::join_all;
8-
use futures::TryStreamExt;
9-
use http::header::HeaderName;
10-
use http::{HeaderMap, HeaderValue, Method};
11-
use noodles::bgzf;
12-
use noodles::vcf;
4+
use http::Method;
135
use reqwest::ClientBuilder;
146
use serde::Deserialize;
157
use serde_json::{json, Value};
168

9+
use crate::http::concat::ConcatResponse;
1710
use htsget_config::types::Class;
1811
use htsget_config::types::Format;
1912

20-
use crate::http_tests::{Header, Response, TestRequest, TestServer};
13+
use crate::http::{Header, Response, TestRequest, TestServer};
2114
use crate::util::expected_bgzf_eof_data_url;
2215
use crate::Config;
2316

@@ -45,65 +38,16 @@ where
4538
.build()
4639
.unwrap();
4740

48-
let merged_response = join_all(
49-
expected_response
50-
.get("htsget")
51-
.unwrap()
52-
.get("urls")
53-
.unwrap()
54-
.as_array()
55-
.unwrap()
56-
.iter()
57-
.map(|url| async {
58-
if let Some(data_uri) = url
59-
.get("url")
60-
.unwrap()
61-
.as_str()
62-
.unwrap()
63-
.strip_prefix("data:;base64,")
64-
{
65-
general_purpose::STANDARD.decode(data_uri).unwrap()
66-
} else {
67-
client
68-
.get(url.get("url").unwrap().as_str().unwrap())
69-
.headers(HeaderMap::from_iter(
70-
url
71-
.get("headers")
72-
.unwrap()
73-
.as_object()
74-
.unwrap_or(&serde_json::Map::new())
75-
.into_iter()
76-
.map(|(key, value)| {
77-
(
78-
HeaderName::from_str(key).unwrap(),
79-
HeaderValue::from_str(value.as_str().unwrap()).unwrap(),
80-
)
81-
}),
82-
))
83-
.send()
84-
.await
85-
.unwrap()
86-
.bytes()
87-
.await
88-
.unwrap()
89-
.to_vec()
90-
}
91-
}),
41+
ConcatResponse::new(
42+
serde_json::from_value(expected_response.get("htsget").unwrap().clone()).unwrap(),
43+
class,
9244
)
45+
.concat_from_client(&client)
46+
.await
47+
.unwrap()
48+
.read_records()
9349
.await
94-
.into_iter()
95-
.reduce(|acc, x| [acc, x].concat())
9650
.unwrap();
97-
98-
let mut reader = vcf::AsyncReader::new(bgzf::AsyncReader::new(merged_response.as_slice()));
99-
let header = reader.read_header().await.unwrap();
100-
println!("{header}");
101-
102-
let mut records = reader.records(&header);
103-
while let Some(record) = records.try_next().await.unwrap() {
104-
println!("{record}");
105-
continue;
106-
}
10751
}
10852

10953
/// Get the expected url path from the formatter.

‎htsget-test/src/lib.rs

+4-11
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,12 @@
1-
#[cfg(any(
2-
feature = "cors-tests",
3-
feature = "server-tests",
4-
feature = "http-tests"
5-
))]
1+
#[cfg(any(feature = "server-tests", feature = "http"))]
62
pub use htsget_config::{
73
config::{Config, DataServerConfig, ServiceInfo, TicketServerConfig},
84
storage::Storage,
95
};
106

117
#[cfg(feature = "aws-mocks")]
128
pub mod aws_mocks;
13-
#[cfg(feature = "cors-tests")]
14-
pub mod cors_tests;
15-
#[cfg(feature = "http-tests")]
16-
pub mod http_tests;
17-
#[cfg(feature = "server-tests")]
18-
pub mod server_tests;
9+
pub mod error;
10+
#[cfg(feature = "http")]
11+
pub mod http;
1912
pub mod util;

0 commit comments

Comments
 (0)
Please sign in to comment.