@@ -343,3 +343,99 @@ def test_export_empty_dest_rejects_existing_file(
343343 dest = "" , # 空文字 — None と同様に既定名ガードが効くこと
344344 recipients = [f"@{ pub_file } " ],
345345 ))
346+
347+
348+ # --- dest 末尾 `/` のファイル名自動補完 (#24, PLAN03-2) ---
349+
350+
351+ def test_complete_dir_dest_s3_trailing_slash (monkeypatch ):
352+ """S3 URI が末尾 `/` のときは既定ファイル名を append する (`aws s3 cp` 互換)"""
353+ from devbase .env .io_export import _complete_dir_dest
354+ monkeypatch .setattr (
355+ "devbase.env.io_export._default_filename" ,
356+ lambda fu : "devbase-env-FIXED.dbenv" ,
357+ )
358+ assert _complete_dir_dest ("s3://bucket/prefix/" , False ) == \
359+ "s3://bucket/prefix/devbase-env-FIXED.dbenv"
360+
361+
362+ def test_complete_dir_dest_s3_full_key_unchanged (monkeypatch ):
363+ """S3 フルキー (末尾 `/` なし) はそのまま返す (回帰防止)"""
364+ from devbase .env .io_export import _complete_dir_dest
365+ monkeypatch .setattr (
366+ "devbase.env.io_export._default_filename" ,
367+ lambda fu : "devbase-env-FIXED.dbenv" ,
368+ )
369+ assert _complete_dir_dest ("s3://bucket/prefix/foo.dbenv" , False ) == \
370+ "s3://bucket/prefix/foo.dbenv"
371+
372+
373+ def test_complete_dir_dest_local_existing_dir (tmp_path , monkeypatch ):
374+ """ローカル既存ディレクトリのときも補完する"""
375+ from devbase .env .io_export import _complete_dir_dest
376+ monkeypatch .setattr (
377+ "devbase.env.io_export._default_filename" ,
378+ lambda fu : "devbase-env-FIXED.dbenv" ,
379+ )
380+ d = tmp_path / "outdir"
381+ d .mkdir ()
382+ result = _complete_dir_dest (str (d ), False )
383+ assert result == str (d / "devbase-env-FIXED.dbenv" )
384+
385+
386+ def test_complete_dir_dest_local_trailing_slash (tmp_path , monkeypatch ):
387+ """末尾 `/` のローカルパスは (ディレクトリが存在しなくても) 補完する"""
388+ from devbase .env .io_export import _complete_dir_dest
389+ monkeypatch .setattr (
390+ "devbase.env.io_export._default_filename" ,
391+ lambda fu : "devbase-env-FIXED.dbenv" ,
392+ )
393+ target = str (tmp_path / "nodir" ) + "/"
394+ assert _complete_dir_dest (target , False ).endswith ("/nodir/devbase-env-FIXED.dbenv" )
395+
396+
397+ def test_complete_dir_dest_local_normal_file_unchanged (tmp_path , monkeypatch ):
398+ """通常のファイルパスは補完しない"""
399+ from devbase .env .io_export import _complete_dir_dest
400+ monkeypatch .setattr (
401+ "devbase.env.io_export._default_filename" ,
402+ lambda fu : "devbase-env-FIXED.dbenv" ,
403+ )
404+ target = str (tmp_path / "out.dbenv" )
405+ assert _complete_dir_dest (target , False ) == target
406+
407+
408+ def test_complete_dir_dest_stdio_unchanged ():
409+ """stdio (`-`) は補完しない"""
410+ from devbase .env .io_export import _complete_dir_dest
411+ assert _complete_dir_dest ("-" , False ) == "-"
412+
413+
414+ def test_complete_dir_dest_plaintext_suffix (monkeypatch ):
415+ """force_unencrypted=True のときは `.dbenv.tar.gz` で補完される"""
416+ from devbase .env .io_export import _complete_dir_dest , _default_filename
417+ name = _default_filename (True )
418+ assert name .endswith (".dbenv.tar.gz" )
419+ result = _complete_dir_dest ("s3://bucket/prefix/" , True )
420+ assert result .endswith (".dbenv.tar.gz" )
421+ assert result .startswith ("s3://bucket/prefix/" )
422+
423+
424+ def test_export_local_dir_completes_filename (fake_root , age_keys , tmp_path ):
425+ """end-to-end: ローカル既存ディレクトリへの export でファイル名が補完される"""
426+ pub_file , id_file = age_keys
427+ outdir = tmp_path / "outdir"
428+ outdir .mkdir ()
429+ rc = export (fake_root , ExportOptions (
430+ dest = str (outdir ),
431+ recipients = [f"@{ pub_file } " ],
432+ ))
433+ assert rc == 0
434+ # 補完されたファイルが 1 つだけ生成されている
435+ files = list (outdir .iterdir ())
436+ assert len (files ) == 1
437+ assert files [0 ].name .startswith ("devbase-env-" )
438+ assert files [0 ].name .endswith (".dbenv" )
439+ # 内容を復号できる
440+ decrypted = cipher .decrypt (files [0 ].read_bytes (), identities = [str (id_file )])
441+ assert decrypted
0 commit comments