@@ -601,6 +601,69 @@ def fake_run(cmd, cwd=None, env=None):
601601 self .assertTrue ((out_dir / "sample" / "sample.nt" ).exists ())
602602 self .assertTrue ((out_dir / "hdt" / "sample.hdt" ).exists ())
603603
604+ def test_main_full_mode_deletes_nt_with_docker_fallback_on_permission_error (self ):
605+ """Full mode falls back to Docker-based removal when .nt unlink raises PermissionError."""
606+ with tempfile .TemporaryDirectory () as td :
607+ tmp_path = Path (td )
608+ input_dir , rules_path = prepare_inputs (tmp_path )
609+ out_dir = tmp_path / "out"
610+ target_nt = out_dir / "sample" / "sample.nt"
611+ target_nt_resolved = target_nt .resolve ()
612+ commands = []
613+ original_unlink = Path .unlink
614+
615+ def fake_run (cmd , cwd = None , env = None ):
616+ commands .append (cmd )
617+ if "/opt/vcf-rdfizer/run_conversion.sh" in cmd :
618+ target_nt .parent .mkdir (parents = True , exist_ok = True )
619+ target_nt .write_text ("<s> <p> <o> .\n " )
620+ if "/opt/vcf-rdfizer/compression.sh" in cmd :
621+ hdt_dir = out_dir / "hdt"
622+ hdt_dir .mkdir (parents = True , exist_ok = True )
623+ (hdt_dir / "sample.hdt" ).write_text ("fake-hdt\n " )
624+ if isinstance (cmd , list ) and cmd [- 1 ].startswith ("rm -f " ) and "/data/out/sample/sample.nt" in cmd [- 1 ]:
625+ if target_nt_resolved .exists ():
626+ original_unlink (target_nt_resolved )
627+ return 0
628+
629+ def unlink_side_effect (path_obj , * args , ** kwargs ):
630+ if path_obj .resolve () == target_nt_resolved :
631+ raise PermissionError (13 , "Permission denied" , str (path_obj ))
632+ return original_unlink (path_obj , * args , ** kwargs )
633+
634+ old_cwd = os .getcwd ()
635+ os .chdir (tmp_path )
636+ try :
637+ with mock .patch .object (vcf_rdfizer , "run" , side_effect = fake_run ), mock .patch .object (
638+ vcf_rdfizer , "check_docker" , return_value = True
639+ ), mock .patch .object (
640+ vcf_rdfizer , "docker_image_exists" , return_value = True
641+ ), mock .patch .object (
642+ vcf_rdfizer , "discover_tsv_triplets" , return_value = mocked_triplets ()
643+ ), mock .patch ("pathlib.Path.unlink" , autospec = True , side_effect = unlink_side_effect ):
644+ rc = invoke_main (
645+ [
646+ "--input" ,
647+ str (input_dir ),
648+ "--rules" ,
649+ str (rules_path ),
650+ "--out" ,
651+ str (out_dir ),
652+ "--keep-tsv" ,
653+ ]
654+ )
655+ finally :
656+ os .chdir (old_cwd )
657+
658+ self .assertEqual (rc , 0 )
659+ self .assertFalse (target_nt .exists ())
660+ self .assertTrue (
661+ any (
662+ isinstance (cmd , list ) and cmd [- 1 ].startswith ("rm -f " ) and "/data/out/sample/sample.nt" in cmd [- 1 ]
663+ for cmd in commands
664+ )
665+ )
666+
604667 def test_main_ignores_unrelated_existing_tsv_triplets (self ):
605668 """Wrapper converts only triplets that match the CLI-selected VCF snapshot."""
606669 with tempfile .TemporaryDirectory () as td :
0 commit comments