Add plugin update and install reporting

This commit is contained in:
pleb
2026-06-28 12:12:57 -07:00
parent 5a9e873de4
commit 931c1d4f73
9 changed files with 618 additions and 42 deletions
+227 -4
View File
@@ -6,13 +6,15 @@ from pathlib import Path
from zipfile import ZipFile
from plugin_helper.checker import check_lock
from plugin_helper.cli import installed_plugins_report
from plugin_helper.fsutil import sha256_file
from plugin_helper.installer import apply_plan, uninstall_plugin
from plugin_helper.instances import get_instance, list_instances
from plugin_helper.models import Lockfile, LockedPlugin, Registry, RegistryPlugin
from plugin_helper.planner import create_plan
from plugin_helper.scanner import scan_instance
from plugin_helper.state import downloads_dir, load_installed_state
from plugin_helper.state import downloads_dir, load_installed_state, plugin_downloads_dir
from plugin_helper.updates import check_updates
from plugin_helper.userdata import backup_userdata
@@ -46,7 +48,7 @@ class PluginHelperTests(unittest.TestCase):
(instance / "Beat Saber_Data").mkdir()
(instance / "Plugins").mkdir()
asset = downloads_dir(state, "1.40.8") / "Example.dll"
asset = plugin_downloads_dir(state, "1.40.8", "example") / "Example.dll"
asset.write_bytes(b"managed dll")
registry = Registry(
@@ -103,7 +105,7 @@ class PluginHelperTests(unittest.TestCase):
state = work / "state"
instance.mkdir(parents=True)
(instance / "Beat Saber_Data").mkdir()
asset = downloads_dir(state, "1.40.8") / "Example.zip"
asset = plugin_downloads_dir(state, "1.40.8", "example") / "Example.zip"
with ZipFile(asset, "w") as archive:
archive.writestr("Plugins/Example.dll", b"dll")
@@ -144,6 +146,49 @@ class PluginHelperTests(unittest.TestCase):
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:
with tempfile.TemporaryDirectory() as tmp:
work = Path(tmp)
instance = work / "instances" / "1.40.8"
state = work / "state"
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")
plan, _ = create_plan(
instance="1.40.8",
instance_path=instance,
beat_saber_version="1.40.8",
registry=Registry(
{
"example": RegistryPlugin(
id="example",
name="Example",
repo=None,
install_strategy="dll-to-plugins",
)
}
),
lockfile=Lockfile(
beat_saber_version="1.40.8",
instance="1.40.8",
plugins=(
LockedPlugin(
id="example",
repo=None,
tag=None,
asset="Example.dll",
sha256=sha256_file(asset),
),
),
),
state_root=state,
repo_root=work,
)
self.assertEqual(plan["changes"][0]["source"], str(asset))
def test_zip_member_cannot_escape_instance(self) -> None:
with tempfile.TemporaryDirectory() as tmp:
work = Path(tmp)
@@ -151,7 +196,7 @@ class PluginHelperTests(unittest.TestCase):
state = work / "state"
instance.mkdir(parents=True)
(instance / "Beat Saber_Data").mkdir()
asset = downloads_dir(state, "1.40.8") / "Bad.zip"
asset = plugin_downloads_dir(state, "1.40.8", "bad") / "Bad.zip"
with ZipFile(asset, "w") as archive:
archive.writestr("../Bad.dll", b"dll")
@@ -239,6 +284,184 @@ class PluginHelperTests(unittest.TestCase):
self.assertEqual(result["summary"]["errors"], 1)
self.assertEqual(result["plugins"][0]["status"], "error")
def test_installed_plugins_report_includes_locked_version(self) -> None:
registry = Registry(
{
"example": RegistryPlugin(
id="example",
name="Example Plugin",
repo="owner/example",
install_strategy="dll-to-plugins",
)
}
)
lockfile = Lockfile(
beat_saber_version="1.40.8",
instance="1.40.8",
plugins=(
LockedPlugin(
id="example",
repo="owner/example",
tag="v1.2.3",
asset="Example.dll",
sha256="abc123",
),
),
)
report = installed_plugins_report(
installed_state={
"instance": "1.40.8",
"plugins": {
"example": {
"installedAt": "2026-06-14T17:18:40Z",
"files": [{"path": "Plugins/Example.dll"}],
}
},
},
registry=registry,
lockfile=lockfile,
)
self.assertEqual(report["plugins"][0]["name"], "Example Plugin")
self.assertEqual(report["plugins"][0]["version"], "v1.2.3")
self.assertEqual(report["plugins"][0]["asset"], "Example.dll")
self.assertEqual(report["plugins"][0]["fileCount"], 1)
def test_update_check_reports_current_matching_asset(self) -> None:
registry = Registry(
{
"example": RegistryPlugin(
id="example",
name="Example",
repo="owner/example",
asset_patterns=("1.40.8.zip",),
install_strategy="bsipa-zip",
)
}
)
lockfile = Lockfile(
beat_saber_version="1.40.8",
instance="1.40.8",
plugins=(
LockedPlugin(
id="example",
repo="owner/example",
tag="v1.1.0",
asset="1.40.8.zip",
sha256="abc123",
),
),
)
result = check_updates(
registry=registry,
lockfile=lockfile,
fetch_releases=lambda repo: [
{
"tag_name": "v1.1.0",
"published_at": "2026-06-10T00:00:00Z",
"assets": [{"name": "1.40.8.zip", "digest": "sha256:abc123"}],
}
],
)
self.assertEqual(result["summary"]["current"], 1)
self.assertEqual(result["plugins"][0]["status"], "current")
self.assertEqual(result["plugins"][0]["latestAssetSha256"], "abc123")
def test_update_check_reports_new_matching_release(self) -> None:
registry = Registry(
{
"example": RegistryPlugin(
id="example",
name="Example",
repo="owner/example",
asset_patterns=("*.zip",),
install_strategy="bsipa-zip",
)
}
)
lockfile = Lockfile(
beat_saber_version="1.40.8",
instance="1.40.8",
plugins=(
LockedPlugin(
id="example",
repo="owner/example",
tag="v1.1.0",
asset="1.40.8.zip",
sha256="abc123",
),
),
)
result = check_updates(
registry=registry,
lockfile=lockfile,
fetch_releases=lambda repo: [
{
"tag_name": "v1.2.0",
"published_at": "2026-06-12T00:00:00Z",
"assets": [
{"name": "1.29.1.zip"},
{"name": "1.40.8.zip", "browser_download_url": "https://example.invalid/asset"},
],
},
{
"tag_name": "v1.1.0",
"published_at": "2026-06-10T00:00:00Z",
"assets": [{"name": "1.40.8.zip"}],
},
],
)
self.assertEqual(result["summary"]["updates"], 1)
self.assertEqual(result["plugins"][0]["status"], "update")
self.assertEqual(result["plugins"][0]["latestTag"], "v1.2.0")
self.assertEqual(result["plugins"][0]["latestAsset"], "1.40.8.zip")
def test_update_check_reports_replaced_asset_digest(self) -> None:
registry = Registry(
{
"example": RegistryPlugin(
id="example",
name="Example",
repo="owner/example",
asset_patterns=("1.40.8.zip",),
install_strategy="bsipa-zip",
)
}
)
lockfile = Lockfile(
beat_saber_version="1.40.8",
instance="1.40.8",
plugins=(
LockedPlugin(
id="example",
repo="owner/example",
tag="v1.1.0",
asset="1.40.8.zip",
sha256="old",
),
),
)
result = check_updates(
registry=registry,
lockfile=lockfile,
fetch_releases=lambda repo: [
{
"tag_name": "v1.1.0",
"published_at": "2026-06-10T00:00:00Z",
"assets": [{"name": "1.40.8.zip", "digest": "sha256:new"}],
}
],
)
self.assertEqual(result["summary"]["updates"], 1)
self.assertEqual(result["plugins"][0]["status"], "update")
self.assertEqual(result["plugins"][0]["latestAssetSha256"], "new")
if __name__ == "__main__":
unittest.main()