113 lines
3.2 KiB
Go
113 lines
3.2 KiB
Go
//go:build integration
|
|
|
|
package pluginhost_test
|
|
|
|
import (
|
|
"context"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/hashicorp/go-hclog"
|
|
"google.golang.org/protobuf/encoding/protojson"
|
|
|
|
pluginv1 "github.com/Silo-Server/silo-plugin-sdk/pkg/pluginproto/silo/plugin/v1"
|
|
|
|
"github.com/Silo-Server/silo-server/internal/events"
|
|
"github.com/Silo-Server/silo-server/internal/pluginhost"
|
|
)
|
|
|
|
const (
|
|
examplePluginDir = "/opt/worktrees/silo-plugin-sdk-rh/examples/hello-runtime-host"
|
|
expectedEventName = "plugin.example.hello-runtime-host.ping"
|
|
)
|
|
|
|
// capturingPublisher implements pluginhost.EventPublisher and buffers envelopes
|
|
// in a channel so the test can assert on them.
|
|
type capturingPublisher struct {
|
|
ch chan events.Envelope
|
|
}
|
|
|
|
func (c *capturingPublisher) Publish(_ context.Context, env events.Envelope) error {
|
|
select {
|
|
case c.ch <- env:
|
|
default:
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func TestPluginPublishEvent_FlowsToHub(t *testing.T) {
|
|
binPath := buildExamplePlugin(t)
|
|
|
|
// Run the built binary with the "manifest" subcommand. The SDK runtime
|
|
// handles this sub-command by printing the manifest as protojson and
|
|
// exiting. This gives us the manifest with the correct checksum baked in
|
|
// (the checksum is SHA-256 of the binary itself, computed at startup).
|
|
manifestBytes, err := exec.Command(binPath, "manifest").Output()
|
|
if err != nil {
|
|
t.Fatalf("get manifest from binary: %v", err)
|
|
}
|
|
manifest := &pluginv1.PluginManifest{}
|
|
if err := protojson.Unmarshal(manifestBytes, manifest); err != nil {
|
|
t.Fatalf("parse manifest protojson: %v", err)
|
|
}
|
|
|
|
publisher := &capturingPublisher{ch: make(chan events.Envelope, 4)}
|
|
host := pluginhost.NewHost(pluginhost.Config{
|
|
Logger: hclog.NewNullLogger(),
|
|
EventPublisher: publisher,
|
|
LibraryLister: nil,
|
|
})
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
defer cancel()
|
|
|
|
client, err := host.Start(ctx, pluginhost.StartRequest{
|
|
InstallationID: 1,
|
|
BinaryPath: binPath,
|
|
Manifest: manifest,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("host.Start: %v", err)
|
|
}
|
|
defer func() {
|
|
_ = host.Stop(1)
|
|
}()
|
|
|
|
// Obtain the ScheduledTask capability client for the "ping" task.
|
|
taskClient, err := client.ScheduledTask("ping")
|
|
if err != nil {
|
|
t.Fatalf("client.ScheduledTask: %v", err)
|
|
}
|
|
|
|
// Trigger the task. The plugin calls sdkruntime.Host().PublishEvent("ping", ...)
|
|
// which traverses the broker stream back into our capturingPublisher.
|
|
if _, err := taskClient.Run(ctx, &pluginv1.RunScheduledTaskRequest{TaskKey: "ping"}); err != nil {
|
|
t.Fatalf("taskClient.Run: %v", err)
|
|
}
|
|
|
|
select {
|
|
case env := <-publisher.ch:
|
|
if env.Channel != events.ChannelPlugins {
|
|
t.Errorf("channel = %q, want %q", env.Channel, events.ChannelPlugins)
|
|
}
|
|
if env.Event != expectedEventName {
|
|
t.Errorf("event = %q, want %q", env.Event, expectedEventName)
|
|
}
|
|
case <-ctx.Done():
|
|
t.Fatal("timed out waiting for event from plugin")
|
|
}
|
|
}
|
|
|
|
func buildExamplePlugin(t *testing.T) string {
|
|
t.Helper()
|
|
out := filepath.Join(t.TempDir(), "hello-runtime-host")
|
|
cmd := exec.Command("go", "build", "-o", out, ".")
|
|
cmd.Dir = examplePluginDir
|
|
if b, err := cmd.CombinedOutput(); err != nil {
|
|
t.Fatalf("build example plugin: %v\n%s", err, b)
|
|
}
|
|
return out
|
|
}
|