@@ -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