Skip to content

Commit 1264a98

Browse files
committed
add integration test
1 parent 4058c00 commit 1264a98

1 file changed

Lines changed: 108 additions & 0 deletions

File tree

src/azure/mod.rs

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,114 @@ mod tests {
419419
assert_eq!(data, loaded);
420420
}
421421

422+
/// Verifies that `list_with_offset` works against OneLake (Fabric) endpoints.
423+
///
424+
/// OneLake silently ignores the `startFrom` query parameter, so the client
425+
/// must fall back to client-side filtering.
426+
///
427+
/// Set these env vars before running:
428+
/// - `AZURE_STORAGE_TOKEN`: bearer token (e.g. from `az account get-access-token`)
429+
/// - `ONELAKE_URL`: full OneLake table URL, e.g.
430+
/// `https://onelake.dfs.fabric.microsoft.com/<ws>/<item>/Tables/<table>`
431+
///
432+
/// See <https://github.com/apache/arrow-rs-object-store/issues/695>
433+
#[ignore = "Used for manual testing against a real OneLake endpoint."]
434+
#[tokio::test]
435+
async fn test_onelake_list_with_offset() {
436+
let url = std::env::var("ONELAKE_URL").unwrap();
437+
let token = std::env::var("AZURE_STORAGE_TOKEN").unwrap();
438+
439+
let store = MicrosoftAzureBuilder::new()
440+
.with_url(&url)
441+
.with_config(AzureConfigKey::Token, token)
442+
.build()
443+
.unwrap();
444+
445+
// Derive a writable path prefix from the URL
446+
// (skip workspace segment which becomes the container)
447+
let parsed: Url = url.parse().unwrap();
448+
let mut segments = parsed.path_segments().unwrap();
449+
let _workspace = segments.next().unwrap();
450+
let base: String = segments.collect::<Vec<_>>().join("/");
451+
let test_dir = format!("{base}/test_onelake_offset");
452+
453+
// Create test files with predictable ordering
454+
let prefix = Path::from(test_dir.as_str());
455+
let files: Vec<Path> = (b'a'..=b'e')
456+
.map(|c| Path::from(format!("{test_dir}/file_{}.txt", c as char)))
457+
.collect();
458+
let data = Bytes::from("test data");
459+
for file in &files {
460+
store.put(file, data.clone().into()).await.unwrap();
461+
}
462+
463+
// Test 1: Offset at file_b → should return c, d, e (not b)
464+
let offset = Path::from(format!("{test_dir}/file_b.txt"));
465+
let result: Vec<Path> = store
466+
.list_with_offset(Some(&prefix), &offset)
467+
.map_ok(|m| m.location)
468+
.try_collect()
469+
.await
470+
.unwrap();
471+
assert!(
472+
!result.contains(&offset),
473+
"offset file_b should be excluded, got: {result:?}"
474+
);
475+
assert_eq!(result.len(), 3, "expected c/d/e after file_b, got: {result:?}");
476+
477+
// Test 2: Offset at file_a → should return b, c, d, e
478+
let offset = Path::from(format!("{test_dir}/file_a.txt"));
479+
let result: Vec<Path> = store
480+
.list_with_offset(Some(&prefix), &offset)
481+
.map_ok(|m| m.location)
482+
.try_collect()
483+
.await
484+
.unwrap();
485+
assert!(!result.contains(&offset), "offset file_a should be excluded");
486+
assert_eq!(result.len(), 4, "expected b/c/d/e after file_a, got: {result:?}");
487+
488+
// Test 3: Offset at file_e (last) → should return empty
489+
let offset = Path::from(format!("{test_dir}/file_e.txt"));
490+
let result: Vec<Path> = store
491+
.list_with_offset(Some(&prefix), &offset)
492+
.map_ok(|m| m.location)
493+
.try_collect()
494+
.await
495+
.unwrap();
496+
assert!(result.is_empty(), "offset at last file should return empty, got: {result:?}");
497+
498+
// Test 4: Offset before all files → should return all 5
499+
let offset = Path::from(format!("{test_dir}/file"));
500+
let result: Vec<Path> = store
501+
.list_with_offset(Some(&prefix), &offset)
502+
.map_ok(|m| m.location)
503+
.try_collect()
504+
.await
505+
.unwrap();
506+
assert_eq!(result.len(), 5, "offset before all files should return all, got: {result:?}");
507+
508+
// Test 5: Every returned entry is strictly greater than offset
509+
let offset = Path::from(format!("{test_dir}/file_c.txt"));
510+
let result: Vec<ObjectMeta> = store
511+
.list_with_offset(Some(&prefix), &offset)
512+
.try_collect()
513+
.await
514+
.unwrap();
515+
for meta in &result {
516+
assert!(
517+
meta.location > offset,
518+
"entry {} should be > offset {}",
519+
meta.location,
520+
offset
521+
);
522+
}
523+
524+
// Cleanup
525+
for file in &files {
526+
let _ = store.delete(file).await;
527+
}
528+
}
529+
422530
#[test]
423531
fn azure_test_config_get_value() {
424532
let azure_client_id = "object_store:fake_access_key_id".to_string();

0 commit comments

Comments
 (0)