Files
wanderer/db/pluginsystem/manifest_test.go
slothful-vassal 485ec53f6d feat: add plugin system (#1034)
* feat: add plugin system

* fix db docker build

* fix hammerhead readme, add strava subscription news to docs

* fixes and sdk improvements

* fix: reduce Meilisearch load, debounce federation sync (#1012)

* optimize meili trail index

* several fixes

---------

Co-authored-by: Flomp <Flomp@users.noreply.github.com>

* Bump svelte from 5.55.5 to 5.56.0 in /docs (#1032)

Bumps [svelte](https://github.com/sveltejs/svelte/tree/HEAD/packages/svelte) from 5.55.5 to 5.56.0.
- [Release notes](https://github.com/sveltejs/svelte/releases)
- [Changelog](https://github.com/sveltejs/svelte/blob/main/packages/svelte/CHANGELOG.md)
- [Commits](https://github.com/sveltejs/svelte/commits/svelte@5.56.0/packages/svelte)

---
updated-dependencies:
- dependency-name: svelte
  dependency-version: 5.56.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Flomp <Flomp@users.noreply.github.com>

* Release v0.19.2 (#1035)

* chore: release v0.19.2

* add changelog

---------

Co-authored-by: Flomp <26000991+Flomp@users.noreply.github.com>
Co-authored-by: Christian Beutel <>

* speed up plugin sync and several small fixes

* concepts for security improvements and process stability

* improve concept

* security concept implemented

* remove insecure TLS

* worker concept implemented

* fixes and cleanup

* fixes

* docu

* mermaid, namings

* WASM plugin host improvements, plugin logging

* fix db migration

* Improve plugin config and category mapping UI

* fixes

* further fixes

* remove manual test sync

* fix db migration and strava mapping

* type added, UI improvements

* fix plugin card toggle clickable area

* optimize synch status card layout

* plugin type 'trails' instead of 'integration'

* session auth validation in UI

* fix komoot date and waypoints

* improve category mapping

* fix send to hammerhead: trail name

* plugin setup error handling improved

* fix review findings

* re-mapping added

* rename remote_category

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Flomp <Flomp@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Flomp <26000991+Flomp@users.noreply.github.com>
2026-06-22 15:00:44 +02:00

223 lines
6.1 KiB
Go

package pluginsystem
import (
"os"
"path/filepath"
"strings"
"testing"
)
func TestValidateManifestAcceptsHammerheadShape(t *testing.T) {
manifest := hammerheadManifestForTest()
if err := ValidateManifest(manifest); err != nil {
t.Fatalf("unexpected error: %v", err)
}
}
func TestValidateManifestRejectsUnknownAuthPermission(t *testing.T) {
manifest := hammerheadManifestForTest()
manifest.Permissions.Auth = []string{"missing"}
if err := ValidateManifest(manifest); err == nil {
t.Fatal("expected error")
}
}
func TestLoadLocalPluginRequiresRelativeEntrypoint(t *testing.T) {
dir := t.TempDir()
manifest := hammerheadManifestForTest()
manifest.Runtime.Entrypoint = "/tmp/plugin.wasm"
writeManifest(t, dir, manifest)
if _, err := LoadLocalPlugin(dir); err == nil {
t.Fatal("expected error")
}
}
func TestLoadLocalPluginsSkipsMissingPluginDir(t *testing.T) {
plugins, err := LoadLocalPlugins(filepath.Join(t.TempDir(), "missing"))
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(plugins) != 0 {
t.Fatalf("got %d plugins, want 0", len(plugins))
}
}
func TestLoadLocalPluginsFindsDirectChildPlugins(t *testing.T) {
root := t.TempDir()
writePluginDir(t, root, "hammerhead")
writePluginDir(t, root, "komoot")
plugins, err := LoadLocalPlugins(root)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(plugins) != 2 {
t.Fatalf("got %d plugins, want 2", len(plugins))
}
}
func TestDiscoverLocalPluginsReportsMissingManifest(t *testing.T) {
root := t.TempDir()
brokenDir := filepath.Join(root, "komoot")
if err := os.MkdirAll(brokenDir, 0o700); err != nil {
t.Fatalf("mkdir plugin dir: %v", err)
}
plugins, issues, err := DiscoverLocalPlugins(root)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(plugins) != 0 {
t.Fatalf("got %d plugins, want 0", len(plugins))
}
if len(issues) != 1 {
t.Fatalf("got %d issues, want 1", len(issues))
}
if issues[0].ID != "komoot" || issues[0].Name != "komoot" || issues[0].Dir != brokenDir {
t.Fatalf("unexpected issue: %#v", issues[0])
}
if issues[0].Error == "" || !strings.Contains(issues[0].Error, "plugin.json") {
t.Fatalf("expected useful plugin.json error, got %#v", issues[0])
}
}
func TestLoadLocalPluginsIgnoresMissingManifestForCompatibility(t *testing.T) {
root := t.TempDir()
if err := os.MkdirAll(filepath.Join(root, "komoot"), 0o700); err != nil {
t.Fatalf("mkdir plugin dir: %v", err)
}
plugins, err := LoadLocalPlugins(root)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(plugins) != 0 {
t.Fatalf("got %d plugins, want 0", len(plugins))
}
}
func TestLoadLocalPluginsSkipsUnsupportedPluginTypes(t *testing.T) {
root := t.TempDir()
writePluginDir(t, root, "hammerhead")
assetsDir := filepath.Join(root, "immich")
if err := os.MkdirAll(assetsDir, 0o700); err != nil {
t.Fatalf("mkdir plugin dir: %v", err)
}
manifest := hammerheadManifestForTest()
manifest.ID = "immich"
manifest.Name = "Immich"
manifest.Type = "assets"
writeManifest(t, assetsDir, manifest)
if err := os.WriteFile(filepath.Join(assetsDir, "plugin.wasm"), []byte("wasm"), 0o600); err != nil {
t.Fatalf("write wasm: %v", err)
}
plugins, err := LoadLocalPlugins(root)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(plugins) != 1 {
t.Fatalf("got %d plugins, want 1", len(plugins))
}
if plugins[0].Manifest.ID != "hammerhead" {
t.Fatalf("got plugin %q, want hammerhead", plugins[0].Manifest.ID)
}
}
func TestLoadLocalPluginsDoesNotSearchRecursively(t *testing.T) {
root := t.TempDir()
writePluginDir(t, filepath.Join(root, "nested"), "hammerhead")
plugins, err := LoadLocalPlugins(root)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(plugins) != 0 {
t.Fatalf("got %d plugins, want 0", len(plugins))
}
}
func hammerheadManifestForTest() Manifest {
return Manifest{
ManifestVersion: ManifestVersion,
ID: "hammerhead",
Type: PluginTypeTrails,
Name: "Hammerhead",
Version: "0.1.0",
Runtime: RuntimeManifest{
Type: RuntimeWASM,
Entrypoint: "plugin.wasm",
},
Capabilities: []CapabilityManifest{
{Name: "prepare_trail_send", Version: "v1", Export: "prepare_trail_send_v1"},
},
Auth: AuthManifest{
Contexts: map[string]AuthContext{
"provider_session": {
Type: AuthTypeSession,
SecretFields: []string{"email", "password"},
Refresh: &AuthRefresh{
Mode: AuthRefreshModePlugin,
Function: "refresh_session_v1",
},
},
},
},
Permissions: PermissionManifest{
Network: NetworkPermissions{
Connectors: []ConnectorTargetPermission{{
Name: "api",
Type: ConnectorTypePublicAPI,
FixedBaseURL: "https://dashboard.hammerhead.io",
AllowedPathPrefixes: []string{"/v1"},
Auth: []string{"provider_session"},
}},
},
Auth: []string{"provider_session"},
Uploads: UploadPermissions{
MaxBytes: 10 << 20,
ContentTypes: []string{"application/gpx+xml", "application/xml"},
},
},
}
}
func writeManifest(t *testing.T, dir string, manifest Manifest) {
t.Helper()
data := []byte(`{
"manifestVersion": "1.0",
"id": "` + manifest.ID + `",
"type": "` + manifest.Type + `",
"name": "` + manifest.Name + `",
"version": "` + manifest.Version + `",
"runtime": {
"type": "` + manifest.Runtime.Type + `",
"entrypoint": "` + manifest.Runtime.Entrypoint + `"
},
"capabilities": [
{"name": "prepare_trail_send", "version": "v1", "export": "prepare_trail_send_v1"}
]
}`)
if err := os.WriteFile(filepath.Join(dir, "plugin.json"), data, 0o600); err != nil {
t.Fatalf("write manifest: %v", err)
}
}
func writePluginDir(t *testing.T, root string, id string) {
t.Helper()
dir := filepath.Join(root, id)
if err := os.MkdirAll(dir, 0o700); err != nil {
t.Fatalf("mkdir plugin dir: %v", err)
}
manifest := hammerheadManifestForTest()
manifest.ID = id
manifest.Name = id
writeManifest(t, dir, manifest)
if err := os.WriteFile(filepath.Join(dir, "plugin.wasm"), []byte("wasm"), 0o600); err != nil {
t.Fatalf("write wasm: %v", err)
}
}