@@ -1797,6 +1797,74 @@ mod tests {
17971797 assert_eq ! ( file. path, "Season/E01.mkv" ) ;
17981798 }
17991799
1800+ #[ tokio:: test]
1801+ async fn directory_checkpoint_accepts_missing_ancestor_of_derived_target_root ( ) {
1802+ let harness = RelocationTestHarness :: new ( BTreeMap :: new ( ) ) . await ;
1803+ let checkpoint = copy_checkpoint (
1804+ "/cmcc/Download/media/云母/动漫" ,
1805+ 0 ,
1806+ CopyCheckpointOperation :: CreateDirectory ,
1807+ CopyCheckpointPhase :: Prepared ,
1808+ ) ;
1809+ let mut job = harness
1810+ . seed_job (
1811+ "copy_submitting" ,
1812+ Some ( checkpoint. clone ( ) ) ,
1813+ None ,
1814+ "Show/E01.mkv" ,
1815+ 0 ,
1816+ )
1817+ . await ;
1818+ job. target_openlist_path = "/cmcc/Download/media/云母/动漫/动画/2024" . to_string ( ) ;
1819+
1820+ let ( index, file) = validate_copy_checkpoint ( & checkpoint, & job) . unwrap ( ) ;
1821+
1822+ assert_eq ! ( index, 0 ) ;
1823+ assert_eq ! ( file. path, "Show/E01.mkv" ) ;
1824+ }
1825+
1826+ #[ tokio:: test]
1827+ async fn directory_checkpoint_rejects_paths_outside_current_target_parent_chain ( ) {
1828+ let harness = RelocationTestHarness :: new ( BTreeMap :: new ( ) ) . await ;
1829+ let initial_checkpoint = copy_checkpoint (
1830+ "/cmcc/Download/media/云母/动漫" ,
1831+ 0 ,
1832+ CopyCheckpointOperation :: CreateDirectory ,
1833+ CopyCheckpointPhase :: Prepared ,
1834+ ) ;
1835+ let mut job = harness
1836+ . seed_job (
1837+ "copy_submitting" ,
1838+ Some ( initial_checkpoint) ,
1839+ None ,
1840+ "Show/E01.mkv" ,
1841+ 0 ,
1842+ )
1843+ . await ;
1844+ job. target_openlist_path = "/cmcc/Download/media/云母/动漫/动画/2024" . to_string ( ) ;
1845+
1846+ for path in [
1847+ "/cmcc/Download/media/云母/电影" ,
1848+ "/cmcc/Download/media/云母/动漫2" ,
1849+ "/cmcc/Download/media/云母/动漫/动画/2024/Other" ,
1850+ "/cmcc/Download/media/云母/动漫/动画/2024/Show/E01.mkv" ,
1851+ ] {
1852+ let checkpoint = copy_checkpoint (
1853+ path,
1854+ 0 ,
1855+ CopyCheckpointOperation :: CreateDirectory ,
1856+ CopyCheckpointPhase :: Prepared ,
1857+ ) ;
1858+
1859+ let error = validate_copy_checkpoint ( & checkpoint, & job) . unwrap_err ( ) ;
1860+
1861+ assert ! (
1862+ error. contains( "不在当前目标文件的父目录链中" ) ,
1863+ "unexpected validation result for {path}: {error}"
1864+ ) ;
1865+ }
1866+ }
1867+
18001868 #[ test]
18011869 fn qb_transfer_completion_rejects_stale_or_unstable_states ( ) {
18021870 assert ! ( !torrent_is_complete( 1 , 99 , 100 , 0.99 , "downloading" ) ) ;
@@ -5648,14 +5716,10 @@ fn validate_copy_checkpoint(
56485716 . rsplit_once ( '/' )
56495717 . map ( |( parent, _) | if parent. is_empty ( ) { "/" } else { parent } )
56505718 . ok_or ( "目录 checkpoint 对应的目标文件路径无效" ) ?;
5651- let target_root = normalize_path ( & job. target_openlist_path ) ?;
56525719 let directory = normalize_path ( & checkpoint. path ) ?;
5653- let target_root_identity = openlist_identity_key ( & target_root) ;
56545720 let directory_identity = openlist_identity_key ( & directory) ;
56555721 let target_parent_identity = openlist_identity_key ( target_parent) ;
5656- if !is_path_prefix ( & target_root_identity, & directory_identity)
5657- || !is_path_prefix ( & directory_identity, & target_parent_identity)
5658- {
5722+ if !is_path_prefix ( & directory_identity, & target_parent_identity) {
56595723 return Err ( format ! (
56605724 "目录 checkpoint 不在当前目标文件的父目录链中: {directory}"
56615725 ) ) ;
0 commit comments