Track install state by installation id
This commit is contained in:
+125
-12
@@ -26,7 +26,14 @@ from plugin_helper.models import Dependency, Lockfile, LockedPlugin, Registry, R
|
||||
from plugin_helper.operations import enable_disabled_plugin
|
||||
from plugin_helper.planner import create_plan
|
||||
from plugin_helper.scanner import scan_bootstrap_files, scan_instance
|
||||
from plugin_helper.state import downloads_dir, load_installed_state, plugin_downloads_dir, save_bootstrap_state, save_installed_state
|
||||
from plugin_helper.state import (
|
||||
downloads_dir,
|
||||
installation_id,
|
||||
load_installed_state,
|
||||
plugin_downloads_dir,
|
||||
save_bootstrap_state,
|
||||
save_installed_state,
|
||||
)
|
||||
from plugin_helper.tui import InstallationChoice, PluginHelperTui
|
||||
from plugin_helper.update_runner import run_update
|
||||
from plugin_helper.updates import check_updates
|
||||
@@ -863,10 +870,112 @@ sha256 = "{sha256_file(asset)}"
|
||||
|
||||
self.assertEqual(status, 0)
|
||||
self.assertEqual((instance / "Plugins" / "Example.dll").read_bytes(), b"managed dll")
|
||||
updated = load_installed_state(state, "1.40.8")
|
||||
install_id = installation_id(
|
||||
profile_id=None,
|
||||
root=instance_root,
|
||||
instance="1.40.8",
|
||||
instance_path=instance,
|
||||
)
|
||||
updated = load_installed_state(state, "1.40.8", install_id=install_id)
|
||||
self.assertIn("example", updated["plugins"])
|
||||
self.assertNotIn("example", updated["disabledPlugins"])
|
||||
|
||||
def test_install_states_are_separate_for_duplicate_instance_names(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
work = Path(tmp)
|
||||
linux_root = work / "linux"
|
||||
windows_root = work / "windows"
|
||||
linux_instance = linux_root / "1.40.8"
|
||||
windows_instance = windows_root / "1.40.8"
|
||||
state = work / "state"
|
||||
registry_dir = work / "registry"
|
||||
locks_dir = work / "locks"
|
||||
registry_dir.mkdir()
|
||||
locks_dir.mkdir()
|
||||
for instance in (linux_instance, windows_instance):
|
||||
(instance / "Beat Saber_Data").mkdir(parents=True)
|
||||
(instance / "Plugins").mkdir()
|
||||
|
||||
asset = plugin_downloads_dir(state, "1.40.8", "example") / "Example.dll"
|
||||
asset.write_bytes(b"managed dll")
|
||||
self.assertEqual(asset.parent, state / "cache" / "downloads" / "1.40.8" / "example")
|
||||
(registry_dir / "plugins.toml").write_text(
|
||||
"""
|
||||
[[plugins]]
|
||||
id = "example"
|
||||
name = "Example"
|
||||
repo = "owner/example"
|
||||
asset_patterns = ["*.dll"]
|
||||
install_strategy = "dll-to-plugins"
|
||||
""".lstrip(),
|
||||
encoding="utf-8",
|
||||
)
|
||||
(locks_dir / "1.40.8.lock.toml").write_text(
|
||||
f"""
|
||||
beat_saber_version = "1.40.8"
|
||||
instance = "1.40.8"
|
||||
|
||||
[[plugins]]
|
||||
id = "example"
|
||||
repo = "owner/example"
|
||||
tag = "v1.0.0"
|
||||
asset = "Example.dll"
|
||||
sha256 = "{sha256_file(asset)}"
|
||||
""".lstrip(),
|
||||
encoding="utf-8",
|
||||
)
|
||||
linux_id = installation_id(
|
||||
profile_id="shared",
|
||||
root=linux_root,
|
||||
instance="1.40.8",
|
||||
instance_path=linux_instance,
|
||||
)
|
||||
windows_id = installation_id(
|
||||
profile_id="shared",
|
||||
root=windows_root,
|
||||
instance="1.40.8",
|
||||
instance_path=windows_instance,
|
||||
)
|
||||
disabled_state = {
|
||||
"instance": "1.40.8",
|
||||
"plugins": {},
|
||||
"disabledPlugins": {
|
||||
"example": {
|
||||
"installedAt": "2026-06-14T17:18:40Z",
|
||||
"disabledAt": "2026-06-14T17:20:00Z",
|
||||
"files": [
|
||||
{
|
||||
"path": "Plugins/Example.dll",
|
||||
"sha256": sha256_file(asset),
|
||||
"size": asset.stat().st_size,
|
||||
}
|
||||
],
|
||||
}
|
||||
},
|
||||
}
|
||||
save_installed_state(state, "1.40.8", json.loads(json.dumps(disabled_state)), install_id=linux_id)
|
||||
save_installed_state(state, "1.40.8", json.loads(json.dumps(disabled_state)), install_id=windows_id)
|
||||
|
||||
with patch("plugin_helper.operations.repo_root", return_value=work):
|
||||
result = enable_disabled_plugin(
|
||||
instance="1.40.8",
|
||||
instance_path=linux_instance,
|
||||
state_root=state,
|
||||
plugin_id="example",
|
||||
repo=work,
|
||||
install_id=linux_id,
|
||||
)
|
||||
|
||||
self.assertTrue((linux_instance / "Plugins" / "Example.dll").is_file())
|
||||
self.assertFalse((windows_instance / "Plugins" / "Example.dll").exists())
|
||||
self.assertIn(f"installs/{linux_id}/plans", result["planPath"])
|
||||
linux_state = load_installed_state(state, "1.40.8", install_id=linux_id)
|
||||
windows_state = load_installed_state(state, "1.40.8", install_id=windows_id)
|
||||
self.assertIn("example", linux_state["plugins"])
|
||||
self.assertNotIn("example", linux_state["disabledPlugins"])
|
||||
self.assertEqual(windows_state["plugins"], {})
|
||||
self.assertIn("example", windows_state["disabledPlugins"])
|
||||
|
||||
def test_save_and_restore_known_good_set(self) -> None:
|
||||
from plugin_helper.operations import restore_known_good_set, save_known_good_set
|
||||
from plugin_helper.state import load_known_good_state, save_installed_state
|
||||
@@ -1006,7 +1115,7 @@ instance = "1.40.8"
|
||||
apply_plan(plan, state)
|
||||
self.assertEqual((instance / "IPA" / "Pending" / "Plugins" / "Example.dll").read_bytes(), b"dll")
|
||||
|
||||
def test_plan_still_finds_legacy_flat_downloads(self) -> None:
|
||||
def test_plan_finds_shared_version_downloads(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
work = Path(tmp)
|
||||
instance = work / "instances" / "1.40.8"
|
||||
@@ -1014,7 +1123,7 @@ instance = "1.40.8"
|
||||
instance.mkdir(parents=True)
|
||||
(instance / "Beat Saber_Data").mkdir()
|
||||
asset = downloads_dir(state, "1.40.8") / "Example.dll"
|
||||
asset.write_bytes(b"legacy flat download")
|
||||
asset.write_bytes(b"shared version download")
|
||||
|
||||
plan, _ = create_plan(
|
||||
instance="1.40.8",
|
||||
@@ -2336,6 +2445,7 @@ sha256 = "{sha256_file(asset)}"
|
||||
state,
|
||||
"1.40.8",
|
||||
{"instance": "1.40.8", "plugins": plugins, "disabledPlugins": disabled_plugins},
|
||||
install_id="test",
|
||||
)
|
||||
choice = InstallationChoice(
|
||||
install_id="test",
|
||||
@@ -2405,6 +2515,7 @@ instance = "1.40.8"
|
||||
state,
|
||||
"1.40.8",
|
||||
{"instance": "1.40.8", "plugins": plugins_state, "disabledPlugins": {}},
|
||||
install_id="test",
|
||||
)
|
||||
choice = InstallationChoice(
|
||||
install_id="test",
|
||||
@@ -2440,6 +2551,8 @@ class PluginHelperTuiTests(unittest.IsolatedAsyncioTestCase):
|
||||
table = app.query_one(DataTable)
|
||||
self.assertEqual(table.row_count, 2)
|
||||
self.assertEqual(app.mode, "installations")
|
||||
self.assertEqual(str(table.get_cell_at(Coordinate(0, 3))), "/tmp/state-linux/installs/linux")
|
||||
self.assertEqual(str(table.get_cell_at(Coordinate(1, 3))), "/tmp/state-windows/installs/windows")
|
||||
|
||||
async def test_single_instance_skips_installation_picker(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
@@ -2464,7 +2577,7 @@ class PluginHelperTuiTests(unittest.IsolatedAsyncioTestCase):
|
||||
await pilot.pause()
|
||||
|
||||
self.assertFalse((instance / "Plugins" / "Example.dll").exists())
|
||||
updated = load_installed_state(state, "1.40.8")
|
||||
updated = load_installed_state(state, "1.40.8", install_id=app.choices[0].install_id)
|
||||
self.assertNotIn("example", updated["plugins"])
|
||||
self.assertIn("example", updated["disabledPlugins"])
|
||||
|
||||
@@ -2498,7 +2611,7 @@ class PluginHelperTuiTests(unittest.IsolatedAsyncioTestCase):
|
||||
await pilot.pause()
|
||||
|
||||
self.assertEqual((instance / "Plugins" / "Example.dll").read_bytes(), b"managed dll")
|
||||
updated = load_installed_state(state, "1.40.8")
|
||||
updated = load_installed_state(state, "1.40.8", install_id=app.choices[0].install_id)
|
||||
self.assertIn("example", updated["plugins"])
|
||||
self.assertNotIn("example", updated["disabledPlugins"])
|
||||
|
||||
@@ -2513,7 +2626,7 @@ class PluginHelperTuiTests(unittest.IsolatedAsyncioTestCase):
|
||||
await pilot.pause()
|
||||
|
||||
self.assertEqual((instance / "Plugins" / "Example.dll").read_bytes(), b"managed dll")
|
||||
updated = load_installed_state(state, "1.40.8")
|
||||
updated = load_installed_state(state, "1.40.8", install_id=app.choices[0].install_id)
|
||||
self.assertIn("example", updated["plugins"])
|
||||
self.assertNotIn("example", updated.get("disabledPlugins", {}))
|
||||
|
||||
@@ -2526,7 +2639,7 @@ class PluginHelperTuiTests(unittest.IsolatedAsyncioTestCase):
|
||||
await pilot.pause()
|
||||
|
||||
self.assertFalse((instance / "Plugins" / "Example.dll").exists())
|
||||
updated = load_installed_state(state, "1.40.8")
|
||||
updated = load_installed_state(state, "1.40.8", install_id=app.choices[0].install_id)
|
||||
self.assertNotIn("example", updated["plugins"])
|
||||
self.assertIn("example", updated["disabledPlugins"])
|
||||
self.assertIn("Disabled 1 plugins", app.status_message)
|
||||
@@ -2540,7 +2653,7 @@ class PluginHelperTuiTests(unittest.IsolatedAsyncioTestCase):
|
||||
await pilot.pause()
|
||||
|
||||
self.assertEqual((instance / "Plugins" / "Example.dll").read_bytes(), b"managed dll")
|
||||
updated = load_installed_state(state, "1.40.8")
|
||||
updated = load_installed_state(state, "1.40.8", install_id=app.choices[0].install_id)
|
||||
self.assertIn("example", updated["plugins"])
|
||||
self.assertNotIn("example", updated["disabledPlugins"])
|
||||
self.assertIn("Enabled 1 plugins", app.status_message)
|
||||
@@ -2555,7 +2668,7 @@ class PluginHelperTuiTests(unittest.IsolatedAsyncioTestCase):
|
||||
|
||||
from plugin_helper.state import load_known_good_state
|
||||
|
||||
known_good = load_known_good_state(state, "1.40.8")
|
||||
known_good = load_known_good_state(state, "1.40.8", install_id=app.choices[0].install_id)
|
||||
self.assertEqual(known_good["pluginIds"], ["alpha", "beta"])
|
||||
self.assertIn("Saved known-good set: 2 enabled plugins", app.status_message)
|
||||
|
||||
@@ -2577,7 +2690,7 @@ class PluginHelperTuiTests(unittest.IsolatedAsyncioTestCase):
|
||||
self.assertIn("Restored known-good set", app.status_message)
|
||||
|
||||
self.assertTrue((instance / "Plugins" / "Beta.dll").exists())
|
||||
updated = load_installed_state(state, "1.40.8")
|
||||
updated = load_installed_state(state, "1.40.8", install_id=app.choices[0].install_id)
|
||||
self.assertEqual(set(updated["plugins"]), {"alpha", "beta"})
|
||||
|
||||
async def test_restore_known_good_without_saved_set_reports_status(self) -> None:
|
||||
@@ -2599,7 +2712,7 @@ class PluginHelperTuiTests(unittest.IsolatedAsyncioTestCase):
|
||||
await pilot.pause()
|
||||
|
||||
self.assertEqual((instance / "Plugins" / "Example.dll").read_bytes(), b"changed dll")
|
||||
updated = load_installed_state(state, "1.40.8")
|
||||
updated = load_installed_state(state, "1.40.8", install_id=app.choices[0].install_id)
|
||||
self.assertIn("example", updated["plugins"])
|
||||
self.assertNotIn("example", updated.get("disabledPlugins", {}))
|
||||
self.assertIn("hash mismatch", app.status_message)
|
||||
|
||||
Reference in New Issue
Block a user