diff --git a/plugins/cmd/ndpgen/README.md b/plugins/cmd/ndpgen/README.md index 82ab4f100..d2f67a60c 100644 --- a/plugins/cmd/ndpgen/README.md +++ b/plugins/cmd/ndpgen/README.md @@ -96,7 +96,7 @@ Generated files are named `nd_host_.go` (lowercase) and placed in ` The generator creates: - `nd_host_.go` - Client wrapper code (WASM build) -- `nd_host__stub.go` - Stub code for non-WASM platforms +- `nd_host__stub.go` - Mock implementations for non-WASM platforms (testing) - `doc.go` - Package documentation listing all available services - `go.mod` - Go module file with required dependencies @@ -108,6 +108,48 @@ Each service file includes: - Response struct types and any struct definitions from the service - Wrapper functions that handle memory allocation and JSON parsing +### Testing Plugins with Mocks + +The stub files (`*_stub.go`) contain [testify/mock](https://github.com/stretchr/testify) implementations that allow plugin authors to unit test their code on non-WASM platforms. + +Each host service has: +- A private mock struct embedding `mock.Mock` +- An exported auto-instantiated mock instance (e.g., `host.CacheMock`, `host.ArtworkMock`) +- Wrapper functions that delegate to the mock + +**Example: Testing a plugin that uses the Cache service** + +```go +package myplugin + +import ( + "testing" + + "github.com/navidrome/navidrome/plugins/pdk/go/host" +) + +func TestMyPluginFunction(t *testing.T) { + // Set expectations on the mock + host.CacheMock.On("GetString", "my-key").Return("cached-value", true, nil) + host.CacheMock.On("SetString", "new-key", "new-value", int64(3600)).Return(nil) + + // Call your plugin code that uses host.CacheGetString and host.CacheSetString + result := myPluginFunction() + + // Assert the result + if result != "expected" { + t.Errorf("unexpected result: %s", result) + } + + // Verify all expected calls were made + host.CacheMock.AssertExpectations(t) +} +``` + +**Resetting mocks between tests:** + +If you need to reset mock state between tests, testify's mock doesn't have a built-in reset. Either use separate test functions (testify automatically resets between test runs), or create a helper to set up fresh expectations. + ### Python Client Library When using `-python`, Python client files are generated in a `python/` subdirectory. diff --git a/plugins/cmd/ndpgen/internal/generator.go b/plugins/cmd/ndpgen/internal/generator.go index 23fedb39b..8d264c5e5 100644 --- a/plugins/cmd/ndpgen/internal/generator.go +++ b/plugins/cmd/ndpgen/internal/generator.go @@ -26,12 +26,56 @@ func hostFuncMap(svc Service) template.FuncMap { // Uses private (lowercase) type names for request/response structs. func clientFuncMap(svc Service) template.FuncMap { return template.FuncMap{ - "lower": strings.ToLower, - "title": strings.Title, - "exportName": func(m Method) string { return m.FunctionName(svc.ExportPrefix()) }, - "requestType": func(m Method) string { return m.ClientRequestTypeName(svc.Name) }, - "responseType": func(m Method) string { return m.ClientResponseTypeName(svc.Name) }, - "formatDoc": formatDoc, + "lower": strings.ToLower, + "title": strings.Title, + "exportName": func(m Method) string { return m.FunctionName(svc.ExportPrefix()) }, + "requestType": func(m Method) string { return m.ClientRequestTypeName(svc.Name) }, + "responseType": func(m Method) string { return m.ClientResponseTypeName(svc.Name) }, + "formatDoc": formatDoc, + "mockReturnValues": mockReturnValues, + } +} + +// mockReturnValues generates the testify mock return value accessors for a method. +// For example: args.String(0), args.Bool(1), args.Error(2) +func mockReturnValues(m Method) string { + var parts []string + idx := 0 + + for _, r := range m.Returns { + parts = append(parts, mockAccessor(r.Type, idx)) + idx++ + } + + if m.HasError { + parts = append(parts, fmt.Sprintf("args.Error(%d)", idx)) + } + + return strings.Join(parts, ", ") +} + +// mockAccessor returns the testify mock accessor call for a given type and index. +func mockAccessor(typ string, idx int) string { + switch { + case typ == "string": + return fmt.Sprintf("args.String(%d)", idx) + case typ == "bool": + return fmt.Sprintf("args.Bool(%d)", idx) + case typ == "int": + return fmt.Sprintf("args.Int(%d)", idx) + case typ == "int64": + return fmt.Sprintf("args.Get(%d).(int64)", idx) + case typ == "int32": + return fmt.Sprintf("args.Get(%d).(int32)", idx) + case typ == "float64": + return fmt.Sprintf("args.Get(%d).(float64)", idx) + case typ == "float32": + return fmt.Sprintf("args.Get(%d).(float32)", idx) + case typ == "[]byte": + return fmt.Sprintf("args.Get(%d).([]byte)", idx) + default: + // For slices, maps, pointers, and custom types, use Get with type assertion + return fmt.Sprintf("args.Get(%d).(%s)", idx, typ) } } diff --git a/plugins/cmd/ndpgen/internal/generator_test.go b/plugins/cmd/ndpgen/internal/generator_test.go index f8fc568d2..449b648b6 100644 --- a/plugins/cmd/ndpgen/internal/generator_test.go +++ b/plugins/cmd/ndpgen/internal/generator_test.go @@ -587,7 +587,7 @@ var _ = Describe("Generator", func() { }) Describe("GenerateClientGoStub", func() { - It("should generate valid stub code with panic functions", func() { + It("should generate valid mock code with testify/mock", func() { svc := Service{ Name: "Cache", Permission: "cache", @@ -623,16 +623,96 @@ var _ = Describe("Generator", func() { // Check for package declaration Expect(codeStr).To(ContainSubstring("package ndpdk")) - // Check for stub comment - Expect(codeStr).To(ContainSubstring("stub implementations for non-WASM builds")) + // Check for mock comment + Expect(codeStr).To(ContainSubstring("mock implementations for non-WASM builds")) - // Check for panic in function body - Expect(codeStr).To(ContainSubstring(`panic("ndpdk: CacheGet is only available in WASM plugins")`)) + // Check for testify/mock import + Expect(codeStr).To(ContainSubstring(`"github.com/stretchr/testify/mock"`)) + + // Check for private mock struct + Expect(codeStr).To(ContainSubstring("type mockCacheService struct")) + Expect(codeStr).To(ContainSubstring("mock.Mock")) + + // Check for exported mock instance + Expect(codeStr).To(ContainSubstring("var CacheMock = &mockCacheService{}")) + + // Check for mock method + Expect(codeStr).To(ContainSubstring("func (m *mockCacheService) Get(key string)")) + Expect(codeStr).To(ContainSubstring("m.Called(key)")) + + // Check for wrapper function delegating to mock + Expect(codeStr).To(ContainSubstring("func CacheGet(key string)")) + Expect(codeStr).To(ContainSubstring("return CacheMock.Get(key)")) // Stub files should NOT have request/response types (they're not needed) Expect(codeStr).NotTo(ContainSubstring("Request struct")) Expect(codeStr).NotTo(ContainSubstring("Response struct")) }) + + It("should generate correct mock return values for different types", func() { + svc := Service{ + Name: "Test", + Permission: "test", + Interface: "TestService", + Methods: []Method{ + { + Name: "GetString", + Params: []Param{ + {Name: "key", Type: "string"}, + }, + Returns: []Param{ + {Name: "value", Type: "string"}, + }, + HasError: true, + }, + { + Name: "GetInt64", + Params: []Param{ + {Name: "key", Type: "string"}, + }, + Returns: []Param{ + {Name: "value", Type: "int64"}, + {Name: "exists", Type: "bool"}, + }, + HasError: true, + }, + { + Name: "GetBytes", + Params: []Param{ + {Name: "key", Type: "string"}, + }, + Returns: []Param{ + {Name: "value", Type: "[]byte"}, + }, + HasError: true, + }, + }, + } + + code, err := GenerateClientGoStub(svc, "ndpdk") + Expect(err).NotTo(HaveOccurred()) + + // Verify it's valid Go code + _, err = format.Source(code) + Expect(err).NotTo(HaveOccurred()) + + codeStr := string(code) + + // Check string return uses args.String(0) + Expect(codeStr).To(ContainSubstring("args.String(0)")) + + // Check int64 return uses args.Get(0).(int64) + Expect(codeStr).To(ContainSubstring("args.Get(0).(int64)")) + + // Check bool return uses args.Bool(1) + Expect(codeStr).To(ContainSubstring("args.Bool(1)")) + + // Check []byte return uses args.Get(0).([]byte) + Expect(codeStr).To(ContainSubstring("args.Get(0).([]byte)")) + + // Check error returns use args.Error(N) + Expect(codeStr).To(ContainSubstring("args.Error(")) + }) }) Describe("Integration", func() { diff --git a/plugins/cmd/ndpgen/internal/templates/client_stub.go.tmpl b/plugins/cmd/ndpgen/internal/templates/client_stub.go.tmpl index da779032b..da19df666 100644 --- a/plugins/cmd/ndpgen/internal/templates/client_stub.go.tmpl +++ b/plugins/cmd/ndpgen/internal/templates/client_stub.go.tmpl @@ -1,13 +1,15 @@ // Code generated by ndpgen. DO NOT EDIT. // -// This file contains stub implementations for non-WASM builds. -// These stubs allow IDE support and compilation on non-WASM platforms. -// They panic at runtime since host functions are only available in WASM plugins. +// This file contains mock implementations for non-WASM builds. +// These mocks allow IDE support, compilation, and unit testing on non-WASM platforms. +// Plugin authors can use the exported mock instances to set expectations in tests. // //go:build !wasip1 package {{.Package}} +import "github.com/stretchr/testify/mock" + {{- /* Generate struct definitions (same as main file, needed for type references in function signatures) */ -}} {{- range .Service.Structs}} @@ -22,14 +24,27 @@ type {{.Name}} struct { } {{- end}} -{{- /* Generate stub wrapper functions that panic */ -}} +// mock{{.Service.Name}}Service is the mock implementation for testing. +type mock{{.Service.Name}}Service struct { + mock.Mock +} + +// {{.Service.Name}}Mock is the auto-instantiated mock instance for testing. +// Use this to set expectations: host.{{.Service.Name}}Mock.On("MethodName", args...).Return(values...) +var {{.Service.Name}}Mock = &mock{{.Service.Name}}Service{} {{range .Service.Methods}} -// {{$.Service.Name}}{{.Name}} is a stub that panics on non-WASM platforms. +// {{.Name}} is the mock method for {{$.Service.Name}}{{.Name}}. +func (m *mock{{$.Service.Name}}Service) {{.Name}}({{range $i, $p := .Params}}{{if $i}}, {{end}}{{$p.Name}} {{$p.Type}}{{end}}) {{.ReturnSignature}} { + args := m.Called({{range $i, $p := .Params}}{{if $i}}, {{end}}{{$p.Name}}{{end}}) + return {{mockReturnValues .}} +} + +// {{$.Service.Name}}{{.Name}} delegates to the mock instance. {{- if .Doc}} {{formatDoc .Doc}} {{- end}} func {{$.Service.Name}}{{.Name}}({{range $i, $p := .Params}}{{if $i}}, {{end}}{{$p.Name}} {{$p.Type}}{{end}}) {{.ReturnSignature}} { - panic("{{$.Package}}: {{$.Service.Name}}{{.Name}} is only available in WASM plugins") + return {{$.Service.Name}}Mock.{{.Name}}({{range $i, $p := .Params}}{{if $i}}, {{end}}{{$p.Name}}{{end}}) } {{- end}} diff --git a/plugins/pdk/go/README.md b/plugins/pdk/go/README.md index 19f3a97bc..2bd0155ff 100644 --- a/plugins/pdk/go/README.md +++ b/plugins/pdk/go/README.md @@ -249,3 +249,66 @@ Or use the provided Makefile targets in plugin examples: ```bash make plugin.wasm ``` + +## Testing Plugins + +The PDK includes [testify/mock](https://github.com/stretchr/testify) implementations for all host services, +allowing you to unit test your plugin code on non-WASM platforms (your development machine). + +### Mock Instances + +Each host service has an auto-instantiated mock instance: + +| Service | Mock Instance | +|---------------|--------------------------| +| `Artwork` | `host.ArtworkMock` | +| `Cache` | `host.CacheMock` | +| `Config` | `host.ConfigMock` | +| `KVStore` | `host.KVStoreMock` | +| `Library` | `host.LibraryMock` | +| `Scheduler` | `host.SchedulerMock` | +| `SubsonicAPI` | `host.SubsonicAPIMock` | +| `WebSocket` | `host.WebSocketMock` | + +### Example Test + +```go +package myplugin + +import ( + "testing" + + "github.com/navidrome/navidrome/plugins/pdk/go/host" +) + +func TestMyPluginFunction(t *testing.T) { + // Set expectations on the mock + host.CacheMock.On("GetString", "my-key").Return("cached-value", true, nil) + host.CacheMock.On("SetString", "new-key", "new-value", int64(3600)).Return(nil) + + // Call your plugin code that uses host.CacheGetString / host.CacheSetString + result, err := myPluginFunction() + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + // Assert the result + if result != "expected" { + t.Errorf("unexpected result: %s", result) + } + + // Verify all expected calls were made + host.CacheMock.AssertExpectations(t) +} +``` + +### Running Tests + +Since tests run on your development machine (not WASM), use standard Go testing: + +```bash +go test ./... +``` + +The stub files with mocks are only compiled for non-WASM builds (`//go:build !wasip1`), +so they won't affect your production WASM binary. diff --git a/plugins/pdk/go/go.mod b/plugins/pdk/go/go.mod index 97f807123..4d5fcddfc 100644 --- a/plugins/pdk/go/go.mod +++ b/plugins/pdk/go/go.mod @@ -2,4 +2,14 @@ module github.com/navidrome/navidrome/plugins/pdk/go go 1.25 -require github.com/extism/go-pdk v1.1.3 +require ( + github.com/extism/go-pdk v1.1.3 + github.com/stretchr/testify v1.11.1 +) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/stretchr/objx v0.5.2 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/plugins/pdk/go/go.sum b/plugins/pdk/go/go.sum index c15d38292..af880eb51 100644 --- a/plugins/pdk/go/go.sum +++ b/plugins/pdk/go/go.sum @@ -1,2 +1,14 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/extism/go-pdk v1.1.3 h1:hfViMPWrqjN6u67cIYRALZTZLk/enSPpNKa+rZ9X2SQ= github.com/extism/go-pdk v1.1.3/go.mod h1:Gz+LIU/YCKnKXhgge8yo5Yu1F/lbv7KtKFkiCSzW/P4= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/plugins/pdk/go/host/nd_host_artwork_stub.go b/plugins/pdk/go/host/nd_host_artwork_stub.go index bd1983ded..aa41e440c 100644 --- a/plugins/pdk/go/host/nd_host_artwork_stub.go +++ b/plugins/pdk/go/host/nd_host_artwork_stub.go @@ -1,14 +1,31 @@ // Code generated by ndpgen. DO NOT EDIT. // -// This file contains stub implementations for non-WASM builds. -// These stubs allow IDE support and compilation on non-WASM platforms. -// They panic at runtime since host functions are only available in WASM plugins. +// This file contains mock implementations for non-WASM builds. +// These mocks allow IDE support, compilation, and unit testing on non-WASM platforms. +// Plugin authors can use the exported mock instances to set expectations in tests. // //go:build !wasip1 package host -// ArtworkGetArtistUrl is a stub that panics on non-WASM platforms. +import "github.com/stretchr/testify/mock" + +// mockArtworkService is the mock implementation for testing. +type mockArtworkService struct { + mock.Mock +} + +// ArtworkMock is the auto-instantiated mock instance for testing. +// Use this to set expectations: host.ArtworkMock.On("MethodName", args...).Return(values...) +var ArtworkMock = &mockArtworkService{} + +// GetArtistUrl is the mock method for ArtworkGetArtistUrl. +func (m *mockArtworkService) GetArtistUrl(id string, size int32) (string, error) { + args := m.Called(id, size) + return args.String(0), args.Error(1) +} + +// ArtworkGetArtistUrl delegates to the mock instance. // GetArtistUrl generates a public URL for an artist's artwork. // // Parameters: @@ -17,10 +34,16 @@ package host // // Returns the public URL for the artwork, or an error if generation fails. func ArtworkGetArtistUrl(id string, size int32) (string, error) { - panic("host: ArtworkGetArtistUrl is only available in WASM plugins") + return ArtworkMock.GetArtistUrl(id, size) } -// ArtworkGetAlbumUrl is a stub that panics on non-WASM platforms. +// GetAlbumUrl is the mock method for ArtworkGetAlbumUrl. +func (m *mockArtworkService) GetAlbumUrl(id string, size int32) (string, error) { + args := m.Called(id, size) + return args.String(0), args.Error(1) +} + +// ArtworkGetAlbumUrl delegates to the mock instance. // GetAlbumUrl generates a public URL for an album's artwork. // // Parameters: @@ -29,10 +52,16 @@ func ArtworkGetArtistUrl(id string, size int32) (string, error) { // // Returns the public URL for the artwork, or an error if generation fails. func ArtworkGetAlbumUrl(id string, size int32) (string, error) { - panic("host: ArtworkGetAlbumUrl is only available in WASM plugins") + return ArtworkMock.GetAlbumUrl(id, size) } -// ArtworkGetTrackUrl is a stub that panics on non-WASM platforms. +// GetTrackUrl is the mock method for ArtworkGetTrackUrl. +func (m *mockArtworkService) GetTrackUrl(id string, size int32) (string, error) { + args := m.Called(id, size) + return args.String(0), args.Error(1) +} + +// ArtworkGetTrackUrl delegates to the mock instance. // GetTrackUrl generates a public URL for a track's artwork. // // Parameters: @@ -41,10 +70,16 @@ func ArtworkGetAlbumUrl(id string, size int32) (string, error) { // // Returns the public URL for the artwork, or an error if generation fails. func ArtworkGetTrackUrl(id string, size int32) (string, error) { - panic("host: ArtworkGetTrackUrl is only available in WASM plugins") + return ArtworkMock.GetTrackUrl(id, size) } -// ArtworkGetPlaylistUrl is a stub that panics on non-WASM platforms. +// GetPlaylistUrl is the mock method for ArtworkGetPlaylistUrl. +func (m *mockArtworkService) GetPlaylistUrl(id string, size int32) (string, error) { + args := m.Called(id, size) + return args.String(0), args.Error(1) +} + +// ArtworkGetPlaylistUrl delegates to the mock instance. // GetPlaylistUrl generates a public URL for a playlist's artwork. // // Parameters: @@ -53,5 +88,5 @@ func ArtworkGetTrackUrl(id string, size int32) (string, error) { // // Returns the public URL for the artwork, or an error if generation fails. func ArtworkGetPlaylistUrl(id string, size int32) (string, error) { - panic("host: ArtworkGetPlaylistUrl is only available in WASM plugins") + return ArtworkMock.GetPlaylistUrl(id, size) } diff --git a/plugins/pdk/go/host/nd_host_cache_stub.go b/plugins/pdk/go/host/nd_host_cache_stub.go index 2d44a4900..fbd80d13f 100644 --- a/plugins/pdk/go/host/nd_host_cache_stub.go +++ b/plugins/pdk/go/host/nd_host_cache_stub.go @@ -1,14 +1,31 @@ // Code generated by ndpgen. DO NOT EDIT. // -// This file contains stub implementations for non-WASM builds. -// These stubs allow IDE support and compilation on non-WASM platforms. -// They panic at runtime since host functions are only available in WASM plugins. +// This file contains mock implementations for non-WASM builds. +// These mocks allow IDE support, compilation, and unit testing on non-WASM platforms. +// Plugin authors can use the exported mock instances to set expectations in tests. // //go:build !wasip1 package host -// CacheSetString is a stub that panics on non-WASM platforms. +import "github.com/stretchr/testify/mock" + +// mockCacheService is the mock implementation for testing. +type mockCacheService struct { + mock.Mock +} + +// CacheMock is the auto-instantiated mock instance for testing. +// Use this to set expectations: host.CacheMock.On("MethodName", args...).Return(values...) +var CacheMock = &mockCacheService{} + +// SetString is the mock method for CacheSetString. +func (m *mockCacheService) SetString(key string, value string, ttlSeconds int64) error { + args := m.Called(key, value, ttlSeconds) + return args.Error(0) +} + +// CacheSetString delegates to the mock instance. // SetString stores a string value in the cache. // // Parameters: @@ -18,10 +35,16 @@ package host // // Returns an error if the operation fails. func CacheSetString(key string, value string, ttlSeconds int64) error { - panic("host: CacheSetString is only available in WASM plugins") + return CacheMock.SetString(key, value, ttlSeconds) } -// CacheGetString is a stub that panics on non-WASM platforms. +// GetString is the mock method for CacheGetString. +func (m *mockCacheService) GetString(key string) (string, bool, error) { + args := m.Called(key) + return args.String(0), args.Bool(1), args.Error(2) +} + +// CacheGetString delegates to the mock instance. // GetString retrieves a string value from the cache. // // Parameters: @@ -30,10 +53,16 @@ func CacheSetString(key string, value string, ttlSeconds int64) error { // Returns the value and whether the key exists. If the key doesn't exist // or the stored value is not a string, exists will be false. func CacheGetString(key string) (string, bool, error) { - panic("host: CacheGetString is only available in WASM plugins") + return CacheMock.GetString(key) } -// CacheSetInt is a stub that panics on non-WASM platforms. +// SetInt is the mock method for CacheSetInt. +func (m *mockCacheService) SetInt(key string, value int64, ttlSeconds int64) error { + args := m.Called(key, value, ttlSeconds) + return args.Error(0) +} + +// CacheSetInt delegates to the mock instance. // SetInt stores an integer value in the cache. // // Parameters: @@ -43,10 +72,16 @@ func CacheGetString(key string) (string, bool, error) { // // Returns an error if the operation fails. func CacheSetInt(key string, value int64, ttlSeconds int64) error { - panic("host: CacheSetInt is only available in WASM plugins") + return CacheMock.SetInt(key, value, ttlSeconds) } -// CacheGetInt is a stub that panics on non-WASM platforms. +// GetInt is the mock method for CacheGetInt. +func (m *mockCacheService) GetInt(key string) (int64, bool, error) { + args := m.Called(key) + return args.Get(0).(int64), args.Bool(1), args.Error(2) +} + +// CacheGetInt delegates to the mock instance. // GetInt retrieves an integer value from the cache. // // Parameters: @@ -55,10 +90,16 @@ func CacheSetInt(key string, value int64, ttlSeconds int64) error { // Returns the value and whether the key exists. If the key doesn't exist // or the stored value is not an integer, exists will be false. func CacheGetInt(key string) (int64, bool, error) { - panic("host: CacheGetInt is only available in WASM plugins") + return CacheMock.GetInt(key) } -// CacheSetFloat is a stub that panics on non-WASM platforms. +// SetFloat is the mock method for CacheSetFloat. +func (m *mockCacheService) SetFloat(key string, value float64, ttlSeconds int64) error { + args := m.Called(key, value, ttlSeconds) + return args.Error(0) +} + +// CacheSetFloat delegates to the mock instance. // SetFloat stores a float value in the cache. // // Parameters: @@ -68,10 +109,16 @@ func CacheGetInt(key string) (int64, bool, error) { // // Returns an error if the operation fails. func CacheSetFloat(key string, value float64, ttlSeconds int64) error { - panic("host: CacheSetFloat is only available in WASM plugins") + return CacheMock.SetFloat(key, value, ttlSeconds) } -// CacheGetFloat is a stub that panics on non-WASM platforms. +// GetFloat is the mock method for CacheGetFloat. +func (m *mockCacheService) GetFloat(key string) (float64, bool, error) { + args := m.Called(key) + return args.Get(0).(float64), args.Bool(1), args.Error(2) +} + +// CacheGetFloat delegates to the mock instance. // GetFloat retrieves a float value from the cache. // // Parameters: @@ -80,10 +127,16 @@ func CacheSetFloat(key string, value float64, ttlSeconds int64) error { // Returns the value and whether the key exists. If the key doesn't exist // or the stored value is not a float, exists will be false. func CacheGetFloat(key string) (float64, bool, error) { - panic("host: CacheGetFloat is only available in WASM plugins") + return CacheMock.GetFloat(key) } -// CacheSetBytes is a stub that panics on non-WASM platforms. +// SetBytes is the mock method for CacheSetBytes. +func (m *mockCacheService) SetBytes(key string, value []byte, ttlSeconds int64) error { + args := m.Called(key, value, ttlSeconds) + return args.Error(0) +} + +// CacheSetBytes delegates to the mock instance. // SetBytes stores a byte slice in the cache. // // Parameters: @@ -93,10 +146,16 @@ func CacheGetFloat(key string) (float64, bool, error) { // // Returns an error if the operation fails. func CacheSetBytes(key string, value []byte, ttlSeconds int64) error { - panic("host: CacheSetBytes is only available in WASM plugins") + return CacheMock.SetBytes(key, value, ttlSeconds) } -// CacheGetBytes is a stub that panics on non-WASM platforms. +// GetBytes is the mock method for CacheGetBytes. +func (m *mockCacheService) GetBytes(key string) ([]byte, bool, error) { + args := m.Called(key) + return args.Get(0).([]byte), args.Bool(1), args.Error(2) +} + +// CacheGetBytes delegates to the mock instance. // GetBytes retrieves a byte slice from the cache. // // Parameters: @@ -105,10 +164,16 @@ func CacheSetBytes(key string, value []byte, ttlSeconds int64) error { // Returns the value and whether the key exists. If the key doesn't exist // or the stored value is not a byte slice, exists will be false. func CacheGetBytes(key string) ([]byte, bool, error) { - panic("host: CacheGetBytes is only available in WASM plugins") + return CacheMock.GetBytes(key) } -// CacheHas is a stub that panics on non-WASM platforms. +// Has is the mock method for CacheHas. +func (m *mockCacheService) Has(key string) (bool, error) { + args := m.Called(key) + return args.Bool(0), args.Error(1) +} + +// CacheHas delegates to the mock instance. // Has checks if a key exists in the cache. // // Parameters: @@ -116,10 +181,16 @@ func CacheGetBytes(key string) ([]byte, bool, error) { // // Returns true if the key exists and has not expired. func CacheHas(key string) (bool, error) { - panic("host: CacheHas is only available in WASM plugins") + return CacheMock.Has(key) } -// CacheRemove is a stub that panics on non-WASM platforms. +// Remove is the mock method for CacheRemove. +func (m *mockCacheService) Remove(key string) error { + args := m.Called(key) + return args.Error(0) +} + +// CacheRemove delegates to the mock instance. // Remove deletes a value from the cache. // // Parameters: @@ -127,5 +198,5 @@ func CacheHas(key string) (bool, error) { // // Returns an error if the operation fails. Does not return an error if the key doesn't exist. func CacheRemove(key string) error { - panic("host: CacheRemove is only available in WASM plugins") + return CacheMock.Remove(key) } diff --git a/plugins/pdk/go/host/nd_host_kvstore_stub.go b/plugins/pdk/go/host/nd_host_kvstore_stub.go index b843eb57a..1b3ff1e8d 100644 --- a/plugins/pdk/go/host/nd_host_kvstore_stub.go +++ b/plugins/pdk/go/host/nd_host_kvstore_stub.go @@ -1,14 +1,31 @@ // Code generated by ndpgen. DO NOT EDIT. // -// This file contains stub implementations for non-WASM builds. -// These stubs allow IDE support and compilation on non-WASM platforms. -// They panic at runtime since host functions are only available in WASM plugins. +// This file contains mock implementations for non-WASM builds. +// These mocks allow IDE support, compilation, and unit testing on non-WASM platforms. +// Plugin authors can use the exported mock instances to set expectations in tests. // //go:build !wasip1 package host -// KVStoreSet is a stub that panics on non-WASM platforms. +import "github.com/stretchr/testify/mock" + +// mockKVStoreService is the mock implementation for testing. +type mockKVStoreService struct { + mock.Mock +} + +// KVStoreMock is the auto-instantiated mock instance for testing. +// Use this to set expectations: host.KVStoreMock.On("MethodName", args...).Return(values...) +var KVStoreMock = &mockKVStoreService{} + +// Set is the mock method for KVStoreSet. +func (m *mockKVStoreService) Set(key string, value []byte) error { + args := m.Called(key, value) + return args.Error(0) +} + +// KVStoreSet delegates to the mock instance. // Set stores a byte value with the given key. // // Parameters: @@ -17,10 +34,16 @@ package host // // Returns an error if the storage limit would be exceeded or the operation fails. func KVStoreSet(key string, value []byte) error { - panic("host: KVStoreSet is only available in WASM plugins") + return KVStoreMock.Set(key, value) } -// KVStoreGet is a stub that panics on non-WASM platforms. +// Get is the mock method for KVStoreGet. +func (m *mockKVStoreService) Get(key string) ([]byte, bool, error) { + args := m.Called(key) + return args.Get(0).([]byte), args.Bool(1), args.Error(2) +} + +// KVStoreGet delegates to the mock instance. // Get retrieves a byte value from storage. // // Parameters: @@ -28,10 +51,16 @@ func KVStoreSet(key string, value []byte) error { // // Returns the value and whether the key exists. func KVStoreGet(key string) ([]byte, bool, error) { - panic("host: KVStoreGet is only available in WASM plugins") + return KVStoreMock.Get(key) } -// KVStoreDelete is a stub that panics on non-WASM platforms. +// Delete is the mock method for KVStoreDelete. +func (m *mockKVStoreService) Delete(key string) error { + args := m.Called(key) + return args.Error(0) +} + +// KVStoreDelete delegates to the mock instance. // Delete removes a value from storage. // // Parameters: @@ -39,10 +68,16 @@ func KVStoreGet(key string) ([]byte, bool, error) { // // Returns an error if the operation fails. Does not return an error if the key doesn't exist. func KVStoreDelete(key string) error { - panic("host: KVStoreDelete is only available in WASM plugins") + return KVStoreMock.Delete(key) } -// KVStoreHas is a stub that panics on non-WASM platforms. +// Has is the mock method for KVStoreHas. +func (m *mockKVStoreService) Has(key string) (bool, error) { + args := m.Called(key) + return args.Bool(0), args.Error(1) +} + +// KVStoreHas delegates to the mock instance. // Has checks if a key exists in storage. // // Parameters: @@ -50,10 +85,16 @@ func KVStoreDelete(key string) error { // // Returns true if the key exists. func KVStoreHas(key string) (bool, error) { - panic("host: KVStoreHas is only available in WASM plugins") + return KVStoreMock.Has(key) } -// KVStoreList is a stub that panics on non-WASM platforms. +// List is the mock method for KVStoreList. +func (m *mockKVStoreService) List(prefix string) ([]string, error) { + args := m.Called(prefix) + return args.Get(0).([]string), args.Error(1) +} + +// KVStoreList delegates to the mock instance. // List returns all keys matching the given prefix. // // Parameters: @@ -61,11 +102,17 @@ func KVStoreHas(key string) (bool, error) { // // Returns a slice of matching keys. func KVStoreList(prefix string) ([]string, error) { - panic("host: KVStoreList is only available in WASM plugins") + return KVStoreMock.List(prefix) } -// KVStoreGetStorageUsed is a stub that panics on non-WASM platforms. +// GetStorageUsed is the mock method for KVStoreGetStorageUsed. +func (m *mockKVStoreService) GetStorageUsed() (int64, error) { + args := m.Called() + return args.Get(0).(int64), args.Error(1) +} + +// KVStoreGetStorageUsed delegates to the mock instance. // GetStorageUsed returns the total storage used by this plugin in bytes. func KVStoreGetStorageUsed() (int64, error) { - panic("host: KVStoreGetStorageUsed is only available in WASM plugins") + return KVStoreMock.GetStorageUsed() } diff --git a/plugins/pdk/go/host/nd_host_library_stub.go b/plugins/pdk/go/host/nd_host_library_stub.go index 2ddb6e3bd..9ad0d97e7 100644 --- a/plugins/pdk/go/host/nd_host_library_stub.go +++ b/plugins/pdk/go/host/nd_host_library_stub.go @@ -1,13 +1,15 @@ // Code generated by ndpgen. DO NOT EDIT. // -// This file contains stub implementations for non-WASM builds. -// These stubs allow IDE support and compilation on non-WASM platforms. -// They panic at runtime since host functions are only available in WASM plugins. +// This file contains mock implementations for non-WASM builds. +// These mocks allow IDE support, compilation, and unit testing on non-WASM platforms. +// Plugin authors can use the exported mock instances to set expectations in tests. // //go:build !wasip1 package host +import "github.com/stretchr/testify/mock" + // Library represents the Library data structure. // Library represents a music library with metadata. type Library struct { @@ -23,7 +25,22 @@ type Library struct { TotalDuration float64 `json:"totalDuration"` } -// LibraryGetLibrary is a stub that panics on non-WASM platforms. +// mockLibraryService is the mock implementation for testing. +type mockLibraryService struct { + mock.Mock +} + +// LibraryMock is the auto-instantiated mock instance for testing. +// Use this to set expectations: host.LibraryMock.On("MethodName", args...).Return(values...) +var LibraryMock = &mockLibraryService{} + +// GetLibrary is the mock method for LibraryGetLibrary. +func (m *mockLibraryService) GetLibrary(id int32) (*Library, error) { + args := m.Called(id) + return args.Get(0).(*Library), args.Error(1) +} + +// LibraryGetLibrary delegates to the mock instance. // GetLibrary retrieves metadata for a specific library by ID. // // Parameters: @@ -31,13 +48,19 @@ type Library struct { // // Returns the library metadata, or an error if the library is not found. func LibraryGetLibrary(id int32) (*Library, error) { - panic("host: LibraryGetLibrary is only available in WASM plugins") + return LibraryMock.GetLibrary(id) } -// LibraryGetAllLibraries is a stub that panics on non-WASM platforms. +// GetAllLibraries is the mock method for LibraryGetAllLibraries. +func (m *mockLibraryService) GetAllLibraries() ([]Library, error) { + args := m.Called() + return args.Get(0).([]Library), args.Error(1) +} + +// LibraryGetAllLibraries delegates to the mock instance. // GetAllLibraries retrieves metadata for all configured libraries. // // Returns a slice of all libraries with their metadata. func LibraryGetAllLibraries() ([]Library, error) { - panic("host: LibraryGetAllLibraries is only available in WASM plugins") + return LibraryMock.GetAllLibraries() } diff --git a/plugins/pdk/go/host/nd_host_scheduler_stub.go b/plugins/pdk/go/host/nd_host_scheduler_stub.go index 8eefef1ba..3eaa0087a 100644 --- a/plugins/pdk/go/host/nd_host_scheduler_stub.go +++ b/plugins/pdk/go/host/nd_host_scheduler_stub.go @@ -1,14 +1,31 @@ // Code generated by ndpgen. DO NOT EDIT. // -// This file contains stub implementations for non-WASM builds. -// These stubs allow IDE support and compilation on non-WASM platforms. -// They panic at runtime since host functions are only available in WASM plugins. +// This file contains mock implementations for non-WASM builds. +// These mocks allow IDE support, compilation, and unit testing on non-WASM platforms. +// Plugin authors can use the exported mock instances to set expectations in tests. // //go:build !wasip1 package host -// SchedulerScheduleOneTime is a stub that panics on non-WASM platforms. +import "github.com/stretchr/testify/mock" + +// mockSchedulerService is the mock implementation for testing. +type mockSchedulerService struct { + mock.Mock +} + +// SchedulerMock is the auto-instantiated mock instance for testing. +// Use this to set expectations: host.SchedulerMock.On("MethodName", args...).Return(values...) +var SchedulerMock = &mockSchedulerService{} + +// ScheduleOneTime is the mock method for SchedulerScheduleOneTime. +func (m *mockSchedulerService) ScheduleOneTime(delaySeconds int32, payload string, scheduleID string) (string, error) { + args := m.Called(delaySeconds, payload, scheduleID) + return args.String(0), args.Error(1) +} + +// SchedulerScheduleOneTime delegates to the mock instance. // ScheduleOneTime schedules a one-time event to be triggered after the specified delay. // Plugins that use this function must also implement the SchedulerCallback capability // @@ -19,10 +36,16 @@ package host // // Returns the schedule ID that can be used to cancel the job, or an error if scheduling fails. func SchedulerScheduleOneTime(delaySeconds int32, payload string, scheduleID string) (string, error) { - panic("host: SchedulerScheduleOneTime is only available in WASM plugins") + return SchedulerMock.ScheduleOneTime(delaySeconds, payload, scheduleID) } -// SchedulerScheduleRecurring is a stub that panics on non-WASM platforms. +// ScheduleRecurring is the mock method for SchedulerScheduleRecurring. +func (m *mockSchedulerService) ScheduleRecurring(cronExpression string, payload string, scheduleID string) (string, error) { + args := m.Called(cronExpression, payload, scheduleID) + return args.String(0), args.Error(1) +} + +// SchedulerScheduleRecurring delegates to the mock instance. // ScheduleRecurring schedules a recurring event using a cron expression. // Plugins that use this function must also implement the SchedulerCallback capability // @@ -33,10 +56,16 @@ func SchedulerScheduleOneTime(delaySeconds int32, payload string, scheduleID str // // Returns the schedule ID that can be used to cancel the job, or an error if scheduling fails. func SchedulerScheduleRecurring(cronExpression string, payload string, scheduleID string) (string, error) { - panic("host: SchedulerScheduleRecurring is only available in WASM plugins") + return SchedulerMock.ScheduleRecurring(cronExpression, payload, scheduleID) } -// SchedulerCancelSchedule is a stub that panics on non-WASM platforms. +// CancelSchedule is the mock method for SchedulerCancelSchedule. +func (m *mockSchedulerService) CancelSchedule(scheduleID string) error { + args := m.Called(scheduleID) + return args.Error(0) +} + +// SchedulerCancelSchedule delegates to the mock instance. // CancelSchedule cancels a scheduled job identified by its schedule ID. // // This works for both one-time and recurring schedules. Once cancelled, the job will not trigger @@ -44,5 +73,5 @@ func SchedulerScheduleRecurring(cronExpression string, payload string, scheduleI // // Returns an error if the schedule ID is not found or if cancellation fails. func SchedulerCancelSchedule(scheduleID string) error { - panic("host: SchedulerCancelSchedule is only available in WASM plugins") + return SchedulerMock.CancelSchedule(scheduleID) } diff --git a/plugins/pdk/go/host/nd_host_subsonicapi_stub.go b/plugins/pdk/go/host/nd_host_subsonicapi_stub.go index a25f9031d..f9d71a9c0 100644 --- a/plugins/pdk/go/host/nd_host_subsonicapi_stub.go +++ b/plugins/pdk/go/host/nd_host_subsonicapi_stub.go @@ -1,18 +1,35 @@ // Code generated by ndpgen. DO NOT EDIT. // -// This file contains stub implementations for non-WASM builds. -// These stubs allow IDE support and compilation on non-WASM platforms. -// They panic at runtime since host functions are only available in WASM plugins. +// This file contains mock implementations for non-WASM builds. +// These mocks allow IDE support, compilation, and unit testing on non-WASM platforms. +// Plugin authors can use the exported mock instances to set expectations in tests. // //go:build !wasip1 package host -// SubsonicAPICall is a stub that panics on non-WASM platforms. +import "github.com/stretchr/testify/mock" + +// mockSubsonicAPIService is the mock implementation for testing. +type mockSubsonicAPIService struct { + mock.Mock +} + +// SubsonicAPIMock is the auto-instantiated mock instance for testing. +// Use this to set expectations: host.SubsonicAPIMock.On("MethodName", args...).Return(values...) +var SubsonicAPIMock = &mockSubsonicAPIService{} + +// Call is the mock method for SubsonicAPICall. +func (m *mockSubsonicAPIService) Call(uri string) (string, error) { + args := m.Called(uri) + return args.String(0), args.Error(1) +} + +// SubsonicAPICall delegates to the mock instance. // Call executes a Subsonic API request and returns the JSON response. // // The uri parameter should be the Subsonic API path without the server prefix, // e.g., "getAlbumList2?type=random&size=10". The response is returned as raw JSON. func SubsonicAPICall(uri string) (string, error) { - panic("host: SubsonicAPICall is only available in WASM plugins") + return SubsonicAPIMock.Call(uri) } diff --git a/plugins/pdk/go/host/nd_host_websocket_stub.go b/plugins/pdk/go/host/nd_host_websocket_stub.go index 6b9ff001b..23ac382f0 100644 --- a/plugins/pdk/go/host/nd_host_websocket_stub.go +++ b/plugins/pdk/go/host/nd_host_websocket_stub.go @@ -1,14 +1,31 @@ // Code generated by ndpgen. DO NOT EDIT. // -// This file contains stub implementations for non-WASM builds. -// These stubs allow IDE support and compilation on non-WASM platforms. -// They panic at runtime since host functions are only available in WASM plugins. +// This file contains mock implementations for non-WASM builds. +// These mocks allow IDE support, compilation, and unit testing on non-WASM platforms. +// Plugin authors can use the exported mock instances to set expectations in tests. // //go:build !wasip1 package host -// WebSocketConnect is a stub that panics on non-WASM platforms. +import "github.com/stretchr/testify/mock" + +// mockWebSocketService is the mock implementation for testing. +type mockWebSocketService struct { + mock.Mock +} + +// WebSocketMock is the auto-instantiated mock instance for testing. +// Use this to set expectations: host.WebSocketMock.On("MethodName", args...).Return(values...) +var WebSocketMock = &mockWebSocketService{} + +// Connect is the mock method for WebSocketConnect. +func (m *mockWebSocketService) Connect(url string, headers map[string]string, connectionID string) (string, error) { + args := m.Called(url, headers, connectionID) + return args.String(0), args.Error(1) +} + +// WebSocketConnect delegates to the mock instance. // Connect establishes a WebSocket connection to the specified URL. // // Plugins that use this function must also implement the WebSocketCallback capability @@ -22,10 +39,16 @@ package host // Returns the connection ID that can be used to send messages or close the connection, // or an error if the connection fails. func WebSocketConnect(url string, headers map[string]string, connectionID string) (string, error) { - panic("host: WebSocketConnect is only available in WASM plugins") + return WebSocketMock.Connect(url, headers, connectionID) } -// WebSocketSendText is a stub that panics on non-WASM platforms. +// SendText is the mock method for WebSocketSendText. +func (m *mockWebSocketService) SendText(connectionID string, message string) error { + args := m.Called(connectionID, message) + return args.Error(0) +} + +// WebSocketSendText delegates to the mock instance. // SendText sends a text message over an established WebSocket connection. // // Parameters: @@ -34,10 +57,16 @@ func WebSocketConnect(url string, headers map[string]string, connectionID string // // Returns an error if the connection is not found or if sending fails. func WebSocketSendText(connectionID string, message string) error { - panic("host: WebSocketSendText is only available in WASM plugins") + return WebSocketMock.SendText(connectionID, message) } -// WebSocketSendBinary is a stub that panics on non-WASM platforms. +// SendBinary is the mock method for WebSocketSendBinary. +func (m *mockWebSocketService) SendBinary(connectionID string, data []byte) error { + args := m.Called(connectionID, data) + return args.Error(0) +} + +// WebSocketSendBinary delegates to the mock instance. // SendBinary sends binary data over an established WebSocket connection. // // Parameters: @@ -46,10 +75,16 @@ func WebSocketSendText(connectionID string, message string) error { // // Returns an error if the connection is not found or if sending fails. func WebSocketSendBinary(connectionID string, data []byte) error { - panic("host: WebSocketSendBinary is only available in WASM plugins") + return WebSocketMock.SendBinary(connectionID, data) } -// WebSocketCloseConnection is a stub that panics on non-WASM platforms. +// CloseConnection is the mock method for WebSocketCloseConnection. +func (m *mockWebSocketService) CloseConnection(connectionID string, code int32, reason string) error { + args := m.Called(connectionID, code, reason) + return args.Error(0) +} + +// WebSocketCloseConnection delegates to the mock instance. // CloseConnection gracefully closes a WebSocket connection. // // Parameters: @@ -59,5 +94,5 @@ func WebSocketSendBinary(connectionID string, data []byte) error { // // Returns an error if the connection is not found or if closing fails. func WebSocketCloseConnection(connectionID string, code int32, reason string) error { - panic("host: WebSocketCloseConnection is only available in WASM plugins") + return WebSocketMock.CloseConnection(connectionID, code, reason) }