@@ -382,3 +382,55 @@ async def test_exact_project_name_precedes_normalized_alias(source_config, tmp_p
382382 assert report .success and report .concepts == 1
383383 assert (destination / "chosen.md" ).is_file ()
384384 assert not (destination / "a.md" ).exists ()
385+
386+
387+ @pytest .mark .asyncio
388+ @pytest .mark .parametrize ("ignore_name" , [".gitignore" , ".bmignore" ])
389+ async def test_unreadable_ignore_file_aborts_publication (
390+ source_config , tmp_path , monkeypatch , ignore_name
391+ ):
392+ from basic_memory .ignore_utils import get_bmignore_path
393+
394+ root = Path (source_config .projects ["export" ].path )
395+ ignore = root / ignore_name if ignore_name == ".gitignore" else get_bmignore_path ()
396+ ignore .parent .mkdir (parents = True , exist_ok = True )
397+ ignore .write_text ("credentials.json\n " , encoding = "utf-8" )
398+ (root / "credentials.json" ).write_text ("excluded bytes" , encoding = "utf-8" )
399+ destination = tmp_path / "bundle"
400+ destination .mkdir ()
401+ (destination / "keep" ).write_bytes (b"previous bundle" )
402+ original_read = Path .read_text
403+
404+ def denied_read (path , * args , ** kwargs ):
405+ if path == ignore :
406+ raise PermissionError (13 , "ignore rules unreadable" , str (path ))
407+ return original_read (path , * args , ** kwargs )
408+
409+ monkeypatch .setattr (Path , "read_text" , denied_read )
410+ with pytest .raises (PermissionError , match = "ignore rules unreadable" ):
411+ await export_project (source_config , "export" , destination , replace = True )
412+ assert (destination / "keep" ).read_bytes () == b"previous bundle"
413+ monkeypatch .setattr (Path , "read_text" , original_read )
414+ assert (await export_project (source_config , "export" , destination , replace = True )).success
415+ assert not (destination / "credentials.json" ).exists ()
416+
417+
418+ @pytest .mark .parametrize ("empty_bmignore" , [False , True ])
419+ def test_strict_ignore_defaults_do_not_create_files (tmp_path , monkeypatch , empty_bmignore ):
420+ from basic_memory .ignore_utils import (
421+ DEFAULT_IGNORE_PATTERNS ,
422+ create_default_bmignore ,
423+ load_gitignore_patterns ,
424+ )
425+
426+ bmignore = tmp_path / ".bmignore"
427+ monkeypatch .setattr ("basic_memory.ignore_utils.get_bmignore_path" , lambda : bmignore )
428+ if empty_bmignore :
429+ bmignore .write_text ("# no custom rules\n " , encoding = "utf-8" )
430+ assert load_gitignore_patterns (tmp_path , use_gitignore = False , strict = True ) == (
431+ DEFAULT_IGNORE_PATTERNS
432+ )
433+ assert bmignore .exists () is empty_bmignore
434+ if empty_bmignore :
435+ create_default_bmignore ()
436+ assert bmignore .read_text (encoding = "utf-8" ) == "# no custom rules\n "
0 commit comments