325 lines
8.2 KiB
Go
325 lines
8.2 KiB
Go
|
|
// Example test demonstrating how to use the PDK mock for unit testing.
|
||
|
|
// This file is only compiled for non-WASM builds.
|
||
|
|
//
|
||
|
|
//go:build !wasip1
|
||
|
|
|
||
|
|
package pdk_test
|
||
|
|
|
||
|
|
import (
|
||
|
|
"testing"
|
||
|
|
|
||
|
|
"github.com/navidrome/navidrome/plugins/pdk/go/pdk"
|
||
|
|
"github.com/stretchr/testify/mock"
|
||
|
|
)
|
||
|
|
|
||
|
|
// ExamplePlugin demonstrates a simple plugin that uses PDK functions.
|
||
|
|
type ExamplePlugin struct{}
|
||
|
|
|
||
|
|
// ProcessMessage reads input, logs it, and outputs a response.
|
||
|
|
func (p *ExamplePlugin) ProcessMessage() error {
|
||
|
|
// Get configuration
|
||
|
|
prefix, ok := pdk.GetConfig("message_prefix")
|
||
|
|
if !ok {
|
||
|
|
prefix = "Hello"
|
||
|
|
}
|
||
|
|
|
||
|
|
// Read input
|
||
|
|
message := pdk.InputString()
|
||
|
|
|
||
|
|
// Log the message
|
||
|
|
pdk.Log(pdk.LogInfo, "Processing: "+message)
|
||
|
|
|
||
|
|
// Output the response
|
||
|
|
pdk.OutputString(prefix + ", " + message + "!")
|
||
|
|
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestExamplePlugin_ProcessMessage(t *testing.T) {
|
||
|
|
// Reset mock state before the test
|
||
|
|
pdk.ResetMock()
|
||
|
|
|
||
|
|
// Set up expectations
|
||
|
|
pdk.PDKMock.On("GetConfig", "message_prefix").Return("Hi", true)
|
||
|
|
pdk.PDKMock.On("InputString").Return("World")
|
||
|
|
pdk.PDKMock.On("Log", pdk.LogInfo, "Processing: World").Return()
|
||
|
|
pdk.PDKMock.On("OutputString", "Hi, World!").Return()
|
||
|
|
|
||
|
|
// Call the plugin function
|
||
|
|
plugin := &ExamplePlugin{}
|
||
|
|
err := plugin.ProcessMessage()
|
||
|
|
|
||
|
|
// Verify no error
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("unexpected error: %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Verify all expected calls were made
|
||
|
|
pdk.PDKMock.AssertExpectations(t)
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestExamplePlugin_ProcessMessage_DefaultPrefix(t *testing.T) {
|
||
|
|
// Reset mock state before the test
|
||
|
|
pdk.ResetMock()
|
||
|
|
|
||
|
|
// Set up expectations - config key not found
|
||
|
|
pdk.PDKMock.On("GetConfig", "message_prefix").Return("", false)
|
||
|
|
pdk.PDKMock.On("InputString").Return("Test")
|
||
|
|
pdk.PDKMock.On("Log", pdk.LogInfo, "Processing: Test").Return()
|
||
|
|
pdk.PDKMock.On("OutputString", "Hello, Test!").Return()
|
||
|
|
|
||
|
|
// Call the plugin function
|
||
|
|
plugin := &ExamplePlugin{}
|
||
|
|
err := plugin.ProcessMessage()
|
||
|
|
|
||
|
|
// Verify no error
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("unexpected error: %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Verify all expected calls were made
|
||
|
|
pdk.PDKMock.AssertExpectations(t)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Example of testing JSON input/output
|
||
|
|
type Request struct {
|
||
|
|
Name string `json:"name"`
|
||
|
|
Count int `json:"count"`
|
||
|
|
}
|
||
|
|
|
||
|
|
type Response struct {
|
||
|
|
Message string `json:"message"`
|
||
|
|
Total int `json:"total"`
|
||
|
|
}
|
||
|
|
|
||
|
|
func ProcessJSONRequest() error {
|
||
|
|
var req Request
|
||
|
|
if err := pdk.InputJSON(&req); err != nil {
|
||
|
|
pdk.SetError(err)
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
|
||
|
|
resp := Response{
|
||
|
|
Message: "Hello, " + req.Name,
|
||
|
|
Total: req.Count * 2,
|
||
|
|
}
|
||
|
|
|
||
|
|
return pdk.OutputJSON(resp)
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestProcessJSONRequest(t *testing.T) {
|
||
|
|
pdk.ResetMock()
|
||
|
|
|
||
|
|
// Mock InputJSON to populate the request struct
|
||
|
|
pdk.PDKMock.On("InputJSON", mock.AnythingOfType("*pdk_test.Request")).
|
||
|
|
Return(nil).
|
||
|
|
Run(func(args mock.Arguments) {
|
||
|
|
req := args.Get(0).(*Request)
|
||
|
|
req.Name = "Alice"
|
||
|
|
req.Count = 5
|
||
|
|
})
|
||
|
|
|
||
|
|
// Expect OutputJSON with the correct response
|
||
|
|
pdk.PDKMock.On("OutputJSON", Response{
|
||
|
|
Message: "Hello, Alice",
|
||
|
|
Total: 10,
|
||
|
|
}).Return(nil)
|
||
|
|
|
||
|
|
// Call the function
|
||
|
|
err := ProcessJSONRequest()
|
||
|
|
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("unexpected error: %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
pdk.PDKMock.AssertExpectations(t)
|
||
|
|
}
|
||
|
|
|
||
|
|
// =============================================================================
|
||
|
|
// Examples using stub types (Memory, HTTPRequest, HTTPResponse)
|
||
|
|
// =============================================================================
|
||
|
|
|
||
|
|
// FetchData demonstrates a plugin function that makes an HTTP request.
|
||
|
|
func FetchData(url string) ([]byte, error) {
|
||
|
|
// Create and configure the HTTP request
|
||
|
|
// Note: SetHeader and SetBody work directly on the stub - no mocking needed!
|
||
|
|
req := pdk.NewHTTPRequest(pdk.MethodGet, url)
|
||
|
|
req.SetHeader("Accept", "application/json")
|
||
|
|
req.SetHeader("User-Agent", "MyPlugin/1.0")
|
||
|
|
|
||
|
|
// Send the request - this is mocked because it requires host interaction
|
||
|
|
resp := req.Send()
|
||
|
|
|
||
|
|
// Check status - works directly on the stub
|
||
|
|
if resp.Status() != 200 {
|
||
|
|
return nil, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// Return body - works directly on the stub
|
||
|
|
return resp.Body(), nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestFetchData(t *testing.T) {
|
||
|
|
pdk.ResetMock()
|
||
|
|
|
||
|
|
// Create a stub response with test data
|
||
|
|
expectedBody := []byte(`{"result": "success"}`)
|
||
|
|
stubResponse := pdk.NewStubHTTPResponse(200, map[string]string{
|
||
|
|
"Content-Type": "application/json",
|
||
|
|
}, expectedBody)
|
||
|
|
|
||
|
|
// Mock NewHTTPRequest to return a real HTTPRequest struct
|
||
|
|
// The struct methods (SetHeader, SetBody) work without mocking
|
||
|
|
pdk.PDKMock.On("NewHTTPRequest", pdk.MethodGet, "https://api.example.com/data").
|
||
|
|
Return(&pdk.HTTPRequest{})
|
||
|
|
|
||
|
|
// Mock Send to return our stub response
|
||
|
|
pdk.PDKMock.On("Send", mock.AnythingOfType("*pdk.HTTPRequest")).
|
||
|
|
Return(stubResponse)
|
||
|
|
|
||
|
|
// Call the function
|
||
|
|
body, err := FetchData("https://api.example.com/data")
|
||
|
|
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("unexpected error: %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
if string(body) != string(expectedBody) {
|
||
|
|
t.Errorf("expected body %q, got %q", expectedBody, body)
|
||
|
|
}
|
||
|
|
|
||
|
|
pdk.PDKMock.AssertExpectations(t)
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestFetchData_NonOKStatus(t *testing.T) {
|
||
|
|
pdk.ResetMock()
|
||
|
|
|
||
|
|
// Create a stub response with 404 status
|
||
|
|
stubResponse := pdk.NewStubHTTPResponse(404, nil, []byte("Not Found"))
|
||
|
|
|
||
|
|
pdk.PDKMock.On("NewHTTPRequest", pdk.MethodGet, "https://api.example.com/missing").
|
||
|
|
Return(&pdk.HTTPRequest{})
|
||
|
|
pdk.PDKMock.On("Send", mock.AnythingOfType("*pdk.HTTPRequest")).
|
||
|
|
Return(stubResponse)
|
||
|
|
|
||
|
|
// Call the function
|
||
|
|
body, err := FetchData("https://api.example.com/missing")
|
||
|
|
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("unexpected error: %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Should return nil for non-200 status
|
||
|
|
if body != nil {
|
||
|
|
t.Errorf("expected nil body for 404, got %q", body)
|
||
|
|
}
|
||
|
|
|
||
|
|
pdk.PDKMock.AssertExpectations(t)
|
||
|
|
}
|
||
|
|
|
||
|
|
// ProcessMemoryData demonstrates working with Memory type.
|
||
|
|
func ProcessMemoryData(mem pdk.Memory) string {
|
||
|
|
// Memory methods work directly on the stub - no mocking needed!
|
||
|
|
data := mem.ReadBytes()
|
||
|
|
return "Processed " + string(data) + " (length: " + formatUint64(mem.Length()) + ")"
|
||
|
|
}
|
||
|
|
|
||
|
|
func formatUint64(n uint64) string {
|
||
|
|
return string(rune('0' + n%10)) // Simplified for demo
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestProcessMemoryData(t *testing.T) {
|
||
|
|
// Create stub memory with test data - no mocking needed!
|
||
|
|
mem := pdk.NewStubMemory(0, 5, []byte("hello"))
|
||
|
|
|
||
|
|
result := ProcessMemoryData(mem)
|
||
|
|
|
||
|
|
expected := "Processed hello (length: 5)"
|
||
|
|
if result != expected {
|
||
|
|
t.Errorf("expected %q, got %q", expected, result)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// StoreAndRetrieve demonstrates Memory Store/Load methods.
|
||
|
|
func TestMemoryStoreAndLoad(t *testing.T) {
|
||
|
|
// Create empty memory
|
||
|
|
mem := pdk.NewStubMemory(100, 0, nil)
|
||
|
|
|
||
|
|
// Store data - works directly, no mock needed
|
||
|
|
mem.Store([]byte("test data"))
|
||
|
|
|
||
|
|
// Verify the data was stored
|
||
|
|
if mem.Length() != 9 {
|
||
|
|
t.Errorf("expected length 9, got %d", mem.Length())
|
||
|
|
}
|
||
|
|
|
||
|
|
// Load into buffer
|
||
|
|
buffer := make([]byte, 9)
|
||
|
|
mem.Load(buffer)
|
||
|
|
|
||
|
|
if string(buffer) != "test data" {
|
||
|
|
t.Errorf("expected 'test data', got %q", buffer)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Free the memory
|
||
|
|
mem.Free()
|
||
|
|
|
||
|
|
if mem.Length() != 0 {
|
||
|
|
t.Errorf("expected length 0 after free, got %d", mem.Length())
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// HTTPMethodString demonstrates that HTTPMethod.String() works without mocking.
|
||
|
|
func TestHTTPMethodString(t *testing.T) {
|
||
|
|
// These work directly - no mocking needed!
|
||
|
|
tests := []struct {
|
||
|
|
method pdk.HTTPMethod
|
||
|
|
expected string
|
||
|
|
}{
|
||
|
|
{pdk.MethodGet, "GET"},
|
||
|
|
{pdk.MethodPost, "POST"},
|
||
|
|
{pdk.MethodPut, "PUT"},
|
||
|
|
{pdk.MethodDelete, "DELETE"},
|
||
|
|
}
|
||
|
|
|
||
|
|
for _, tc := range tests {
|
||
|
|
result := tc.method.String()
|
||
|
|
if result != tc.expected {
|
||
|
|
t.Errorf("expected %q for method %d, got %q", tc.expected, tc.method, result)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// PostJSON demonstrates a more complex HTTP request with body.
|
||
|
|
func PostJSON(url string, data []byte) (int, error) {
|
||
|
|
req := pdk.NewHTTPRequest(pdk.MethodPost, url)
|
||
|
|
req.SetHeader("Content-Type", "application/json")
|
||
|
|
req.SetBody(data) // Works directly on stub
|
||
|
|
|
||
|
|
resp := req.Send() // This is mocked
|
||
|
|
return int(resp.Status()), nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestPostJSON(t *testing.T) {
|
||
|
|
pdk.ResetMock()
|
||
|
|
|
||
|
|
stubResponse := pdk.NewStubHTTPResponse(201, nil, nil)
|
||
|
|
|
||
|
|
pdk.PDKMock.On("NewHTTPRequest", pdk.MethodPost, "https://api.example.com/items").
|
||
|
|
Return(&pdk.HTTPRequest{})
|
||
|
|
pdk.PDKMock.On("Send", mock.AnythingOfType("*pdk.HTTPRequest")).
|
||
|
|
Return(stubResponse)
|
||
|
|
|
||
|
|
status, err := PostJSON("https://api.example.com/items", []byte(`{"name":"test"}`))
|
||
|
|
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("unexpected error: %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
if status != 201 {
|
||
|
|
t.Errorf("expected status 201, got %d", status)
|
||
|
|
}
|
||
|
|
|
||
|
|
pdk.PDKMock.AssertExpectations(t)
|
||
|
|
}
|