124 lines
3.5 KiB
Go
124 lines
3.5 KiB
Go
package main
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"verstak/internal/core/config"
|
|
"verstak/internal/core/plugins"
|
|
)
|
|
|
|
// TestSetPluginEnabled_BrokenPlugin_Rollback verifies that when activation fails,
|
|
// the plugin is fully rolled back: not Active, not Enabled, and not in config.
|
|
func TestSetPluginEnabled_BrokenPlugin_Rollback(t *testing.T) {
|
|
// Isolate global config to a temp dir so we don't pollute the user's real config
|
|
tmpCfgDir := filepath.Join(t.TempDir(), "config")
|
|
if err := os.MkdirAll(tmpCfgDir, 0o750); err != nil {
|
|
t.Fatalf("mkdir config dir: %v", err)
|
|
}
|
|
t.Setenv("XDG_CONFIG_HOME", tmpCfgDir)
|
|
|
|
vaultRoot := t.TempDir()
|
|
|
|
// Create .verstak/plugins/ structure
|
|
pluginsDir := filepath.Join(vaultRoot, ".verstak", "plugins")
|
|
if err := os.MkdirAll(pluginsDir, 0o750); err != nil {
|
|
t.Fatalf("mkdir plugins: %v", err)
|
|
}
|
|
|
|
// Create a broken plugin: valid Lua but invalid background task interval
|
|
brokenDir := filepath.Join(pluginsDir, "broken")
|
|
if err := os.MkdirAll(brokenDir, 0o750); err != nil {
|
|
t.Fatalf("mkdir broken plugin: %v", err)
|
|
}
|
|
|
|
// plugin.json with an invalid background task interval ("not-a-duration")
|
|
pluginJSON := `{
|
|
"name": "broken",
|
|
"version": "0.1.0",
|
|
"hooks": {
|
|
"on_install": "on_install",
|
|
"on_init": "on_init"
|
|
},
|
|
"background_tasks": [
|
|
{"id": "bad-task", "interval": "not-a-duration", "script": "bad.lua"}
|
|
]
|
|
}`
|
|
if err := os.WriteFile(filepath.Join(brokenDir, "plugin.json"), []byte(pluginJSON), 0o644); err != nil {
|
|
t.Fatalf("write plugin.json: %v", err)
|
|
}
|
|
|
|
// main.lua — on_install is a no-op (tables not needed for this test),
|
|
// on_init is harmless. Activation will fail on the invalid background task interval.
|
|
mainLua := `
|
|
function on_install()
|
|
-- no-op
|
|
end
|
|
|
|
function on_init()
|
|
-- harmless
|
|
end
|
|
`
|
|
if err := os.WriteFile(filepath.Join(brokenDir, "main.lua"), []byte(mainLua), 0o644); err != nil {
|
|
t.Fatalf("write main.lua: %v", err)
|
|
}
|
|
|
|
// Create App with real plugin manager
|
|
app := &App{
|
|
plugins: plugins.NewManager(vaultRoot),
|
|
vault: vaultRoot,
|
|
vaultOpen: true,
|
|
}
|
|
|
|
// Discover the plugin
|
|
app.plugins.Discover()
|
|
|
|
// Step 1: Install the plugin (creates tables, marks installed in config)
|
|
if err := app.plugins.Install("broken"); err != nil {
|
|
t.Fatalf("install broken plugin: %v", err)
|
|
}
|
|
|
|
// Verify plugin is installed
|
|
if !app.plugins.IsInstalled("broken") {
|
|
t.Fatal("expected plugin to be installed")
|
|
}
|
|
|
|
// Step 2: Try to enable — should fail because background task has invalid interval
|
|
err := app.SetPluginEnabled("broken", true)
|
|
if err == nil {
|
|
t.Fatal("expected SetPluginEnabled to fail for broken plugin, got nil")
|
|
}
|
|
t.Logf("SetPluginEnabled returned expected error: %v", err)
|
|
|
|
// Step 3: Verify plugin is NOT Active and NOT Enabled (in-memory rollback)
|
|
found := false
|
|
for _, p := range app.plugins.Plugins() {
|
|
if p.Meta.Name == "broken" {
|
|
found = true
|
|
if p.Active {
|
|
t.Error("expected plugin to NOT be Active after failed activation")
|
|
}
|
|
if p.Enabled {
|
|
t.Error("expected plugin to NOT be Enabled after failed activation (in-memory rollback)")
|
|
}
|
|
}
|
|
}
|
|
if !found {
|
|
t.Fatal("plugin 'broken' not found in manager")
|
|
}
|
|
|
|
// Step 4: Verify plugin is NOT in EnabledPlugins config
|
|
appCfg, err := config.LoadAppConfig()
|
|
if err != nil {
|
|
t.Fatalf("load app config: %v", err)
|
|
}
|
|
if appCfg != nil {
|
|
for _, name := range appCfg.EnabledPlugins {
|
|
if name == "broken" {
|
|
t.Error("expected 'broken' to NOT be in EnabledPlugins config after failed activation")
|
|
}
|
|
}
|
|
}
|
|
}
|