33import tempfile
44import unittest
55from pathlib import Path
6+ from unittest import mock
67
78from scripts .ci import package_windows_portable as MODULE
89
@@ -14,17 +15,30 @@ def make_project_layout(
1415 product_name : str = "AstrBot" ,
1516 cargo_toml : str = '[package]\n name = "astrbot-desktop-tauri"\n ' ,
1617 marker_name : str = "portable.flag\n " ,
18+ tauri_resources : dict [str , str ] | None = None ,
1719 ) -> dict [str , Path ]:
1820 project_root = Path (self .enterContext (tempfile .TemporaryDirectory ()))
1921 script_path = project_root / "scripts" / "ci" / "package_windows_portable.py"
2022 tauri_config_path = project_root / "src-tauri" / "tauri.conf.json"
2123 cargo_toml_path = project_root / "src-tauri" / "Cargo.toml"
2224 marker_path = project_root / MODULE .PORTABLE_RUNTIME_MARKER_RELATIVE_PATH
25+ if tauri_resources is None :
26+ tauri_resources = {
27+ "../resources/backend" : "backend" ,
28+ "../resources/webui" : "webui" ,
29+ }
2330
2431 script_path .parent .mkdir (parents = True )
2532 script_path .write_text ("# placeholder" )
2633 tauri_config_path .parent .mkdir (parents = True )
27- tauri_config_path .write_text (json .dumps ({"productName" : product_name }))
34+ tauri_config_path .write_text (
35+ json .dumps (
36+ {
37+ "productName" : product_name ,
38+ "bundle" : {"resources" : tauri_resources },
39+ }
40+ )
41+ )
2842 cargo_toml_path .write_text (cargo_toml )
2943 marker_path .parent .mkdir (parents = True , exist_ok = True )
3044 marker_path .write_text (marker_name )
@@ -198,6 +212,54 @@ def test_load_project_config_from_returns_root_product_and_marker(self):
198212 self .assertEqual (project_config .product_name , "AstrBot" )
199213 self .assertEqual (project_config .binary_name , "astrbot-desktop-tauri" )
200214 self .assertEqual (project_config .portable_marker_name , "portable.flag" )
215+ self .assertEqual (project_config .backend_layout_relative_path , Path ("backend" ))
216+ self .assertEqual (project_config .webui_layout_relative_path , Path ("webui" ))
217+
218+ def test_load_project_config_from_reads_portable_layout_aliases_from_tauri_resources (
219+ self ,
220+ ):
221+ layout = self .make_project_layout (
222+ tauri_resources = {
223+ "../resources/backend" : "runtime/backend" ,
224+ "../resources/webui" : "runtime/webui" ,
225+ }
226+ )
227+
228+ project_config = MODULE .load_project_config_from (layout ["script_path" ])
229+
230+ self .assertEqual (
231+ project_config .backend_layout_relative_path , Path ("runtime/backend" )
232+ )
233+ self .assertEqual (
234+ project_config .webui_layout_relative_path , Path ("runtime/webui" )
235+ )
236+
237+ def test_load_project_config_from_requires_exact_tauri_resource_source_keys (self ):
238+ layout = self .make_project_layout (
239+ tauri_resources = {
240+ "./../resources/backend" : "runtime/backend" ,
241+ "../resources/webui" : "runtime/webui" ,
242+ }
243+ )
244+
245+ with self .assertRaisesRegex (
246+ ValueError ,
247+ re .escape ("Missing bundle.resources alias for ../resources/backend" ),
248+ ):
249+ MODULE .load_project_config_from (layout ["script_path" ])
250+
251+ def test_load_project_config_from_normalizes_windows_relpath_separators (self ):
252+ layout = self .make_project_layout ()
253+
254+ with mock .patch .object (
255+ MODULE .os .path ,
256+ "relpath" ,
257+ side_effect = [r"..\resources\backend" , r"..\resources\webui" ],
258+ ):
259+ project_config = MODULE .load_project_config_from (layout ["script_path" ])
260+
261+ self .assertEqual (project_config .backend_layout_relative_path , Path ("backend" ))
262+ self .assertEqual (project_config .webui_layout_relative_path , Path ("webui" ))
201263
202264 def test_normalize_legacy_nightly_version_returns_base_version_and_suffix (self ):
203265 self .assertEqual (
@@ -360,6 +422,8 @@ def test_resolve_main_executable_path_uses_binary_name_not_product_name(self):
360422 product_name = "AstrBot" ,
361423 binary_name = "astrbot-desktop-tauri" ,
362424 portable_marker_name = "portable.flag" ,
425+ backend_layout_relative_path = Path ("backend" ),
426+ webui_layout_relative_path = Path ("webui" ),
363427 )
364428
365429 self .assertEqual (
@@ -424,12 +488,20 @@ def test_populate_portable_root_copies_release_bundle_contents(self):
424488 self .assertTrue ((destination_root / "WebView2Loader.dll" ).is_file ())
425489 self .assertTrue (
426490 (
427- destination_root / "resources" / "backend" / "runtime-manifest.json"
491+ destination_root
492+ / project_config .backend_layout_relative_path
493+ / "runtime-manifest.json"
428494 ).is_file ()
429495 )
430496 self .assertTrue (
431- (destination_root / "resources" / "webui" / "index.html" ).is_file ()
497+ (
498+ destination_root
499+ / project_config .webui_layout_relative_path
500+ / "index.html"
501+ ).is_file ()
432502 )
503+ self .assertFalse ((destination_root / "resources" / "backend" ).exists ())
504+ self .assertFalse ((destination_root / "resources" / "webui" ).exists ())
433505 self .assertTrue ((destination_root / "kill-backend-processes.ps1" ).is_file ())
434506 self .assertTrue ((destination_root / "portable.flag" ).is_file ())
435507 self .assertTrue ((destination_root / MODULE .PORTABLE_README_NAME ).is_file ())
@@ -466,6 +538,8 @@ def test_add_portable_runtime_files_writes_marker_and_readme(self):
466538 product_name = "AstrBot" ,
467539 binary_name = "astrbot-desktop-tauri" ,
468540 portable_marker_name = "portable.flag" ,
541+ backend_layout_relative_path = Path ("backend" ),
542+ webui_layout_relative_path = Path ("webui" ),
469543 )
470544
471545 MODULE .add_portable_runtime_files (root , project_config )
@@ -477,34 +551,78 @@ def test_add_portable_runtime_files_writes_marker_and_readme(self):
477551 )
478552
479553 def test_validate_portable_root_accepts_expected_layout (self ):
554+ layout = self .make_project_layout ()
555+ project_config = MODULE .load_project_config_from (layout ["script_path" ])
556+
480557 with tempfile .TemporaryDirectory () as tmpdir :
481558 root = Path (tmpdir )
482559 (root / "AstrBot.exe" ).write_text ("binary" )
483- (root / "resources" / "backend" ).mkdir (parents = True )
484- (root / "resources" / "webui" ).mkdir (parents = True )
485- (root / "resources" / "backend" / "runtime-manifest.json" ).write_text ("{}" )
486- (root / "resources" / "webui" / "index.html" ).write_text ("<html></html>" )
560+ (root / project_config .backend_layout_relative_path ).mkdir (parents = True )
561+ (root / project_config .webui_layout_relative_path ).mkdir (parents = True )
562+ (
563+ root / project_config .backend_layout_relative_path / "runtime-manifest.json"
564+ ).write_text ("{}" )
565+ (root / project_config .webui_layout_relative_path / "index.html" ).write_text (
566+ "<html></html>"
567+ )
568+
569+ MODULE .validate_portable_root (root , project_config )
487570
488- MODULE .validate_portable_root (root )
571+ def test_validate_portable_root_accepts_nested_alias_layout (self ):
572+ layout = self .make_project_layout (
573+ tauri_resources = {
574+ "../resources/backend" : "runtime/backend" ,
575+ "../resources/webui" : "runtime/webui" ,
576+ }
577+ )
578+ project_config = MODULE .load_project_config_from (layout ["script_path" ])
579+
580+ with tempfile .TemporaryDirectory () as tmpdir :
581+ root = Path (tmpdir )
582+ (root / "AstrBot.exe" ).write_text ("binary" )
583+ (root / project_config .portable_marker_name ).write_text ("marker" )
584+ (root / project_config .backend_layout_relative_path ).mkdir (parents = True )
585+ (root / project_config .webui_layout_relative_path ).mkdir (parents = True )
586+ (
587+ root / project_config .backend_layout_relative_path / "runtime-manifest.json"
588+ ).write_text ("{}" )
589+ (root / project_config .webui_layout_relative_path / "index.html" ).write_text (
590+ "<html></html>"
591+ )
592+
593+ MODULE .validate_portable_root (root , project_config )
594+
595+ self .assertFalse ((root / "backend" ).exists ())
596+ self .assertFalse ((root / "webui" ).exists ())
489597
490598 def test_validate_portable_root_requires_expected_files (self ):
599+ layout = self .make_project_layout ()
600+ project_config = MODULE .load_project_config_from (layout ["script_path" ])
601+
491602 with tempfile .TemporaryDirectory () as tmpdir :
492603 root = Path (tmpdir )
493604 (root / "AstrBot.exe" ).write_text ("binary" )
494605
495606 with self .assertRaisesRegex (ValueError , "runtime-manifest.json" ):
496- MODULE .validate_portable_root (root )
607+ MODULE .validate_portable_root (root , project_config )
497608
498609 def test_validate_portable_root_requires_top_level_exe (self ):
610+ layout = self .make_project_layout ()
611+ project_config = MODULE .load_project_config_from (layout ["script_path" ])
612+
499613 with tempfile .TemporaryDirectory () as tmpdir :
500614 root = Path (tmpdir )
501- (root / "resources" / "backend" ).mkdir (parents = True )
502- (root / "resources" / "webui" ).mkdir (parents = True )
503- (root / "resources" / "backend" / "runtime-manifest.json" ).write_text ("{}" )
504- (root / "resources" / "webui" / "index.html" ).write_text ("<html></html>" )
615+ (root / project_config .backend_layout_relative_path ).mkdir (parents = True )
616+ (root / project_config .webui_layout_relative_path ).mkdir (parents = True )
617+ (
618+ root / project_config .backend_layout_relative_path / "runtime-manifest.json"
619+ ).write_text ("{}" )
620+ (root / project_config .webui_layout_relative_path / "index.html" ).write_text (
621+ "<html></html>"
622+ )
505623
506624 with self .assertRaisesRegex (ValueError , r"top-level \*\.exe" ):
507- MODULE .validate_portable_root (root )
625+ MODULE .validate_portable_root (root , project_config )
508626
509627
510628if __name__ == "__main__" :
0 commit comments