"plex did not take radarr.download.completed: Unexpected end of JSON input" read as the runtime failing to parse the bundle's answer. It was plex's own handler error, relayed. A bundle's error answer is now a launch.Refused, and the line says the handler answered an error, quotes it, names the event id and the delay before the next offer; the runtime's own failures (no answer in time, bundle exited) are said as before. The rule, unchanged and now tested: any mesh/event answer that is not an error takes the event, whatever its result says, including none.
90 lines
2.9 KiB
Go
90 lines
2.9 KiB
Go
package launch
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"os"
|
|
"path/filepath"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// A bundle that answers `mesh/event` as $ANSWER says: an error, a result, or an answer with neither.
|
|
const answeringBundle = `#!/bin/sh
|
|
while IFS= read -r line; do
|
|
id=$(printf '%s' "$line" | sed -n 's/.*"id":\([0-9]*\)[,}].*/\1/p')
|
|
case "$line" in
|
|
*'"method":"initialize"'*) printf '{"jsonrpc":"2.0","id":%s,"result":{}}\n' "$id" ;;
|
|
*'"method":"tools/list"'*)
|
|
printf '{"jsonrpc":"2.0","id":%s,"result":{"tools":[]}}\n' "$id"
|
|
printf '{"jsonrpc":"2.0","id":"s1","method":"mesh/subscribe","params":{"pattern":"#"}}\n' ;;
|
|
*'"method":"mesh/event"'*)
|
|
case "$ANSWER" in
|
|
error) printf '{"jsonrpc":"2.0","id":%s,"error":{"code":-32000,"message":"Unexpected end of JSON input"}}\n' "$id" ;;
|
|
result) printf '{"jsonrpc":"2.0","id":%s,"result":{}}\n' "$id" ;;
|
|
bare) printf '{"jsonrpc":"2.0","id":%s}\n' "$id" ;;
|
|
esac ;;
|
|
esac
|
|
done
|
|
`
|
|
|
|
type subscribing struct {
|
|
mu sync.Mutex
|
|
deliver func(json.RawMessage) error
|
|
}
|
|
|
|
func (b *subscribing) Publish(json.RawMessage) error { return nil }
|
|
func (b *subscribing) Ask(json.RawMessage) (json.RawMessage, error) { return nil, nil }
|
|
func (b *subscribing) State(string, json.RawMessage) (json.RawMessage, error) { return nil, nil }
|
|
func (b *subscribing) Watch(json.RawMessage, func(json.RawMessage) error) (func(), error) {
|
|
return func() {}, nil
|
|
}
|
|
func (b *subscribing) Subscribe(d func(json.RawMessage) error) error {
|
|
b.mu.Lock()
|
|
defer b.mu.Unlock()
|
|
b.deliver = d
|
|
return nil
|
|
}
|
|
|
|
func deliverTo(t *testing.T, answer string) error {
|
|
t.Helper()
|
|
entry := filepath.Join(t.TempDir(), "bundle")
|
|
if err := os.WriteFile(entry, []byte(answeringBundle), 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
b := &subscribing{}
|
|
l, err := Start("plex", entry, append(os.Environ(), "ANSWER="+answer), b, t.Logf)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(l.Stop)
|
|
for i := 0; ; i++ {
|
|
b.mu.Lock()
|
|
d := b.deliver
|
|
b.mu.Unlock()
|
|
if d != nil {
|
|
return d(json.RawMessage(`{"key":"radarr.download.completed","body":{"title":"x"}}`))
|
|
}
|
|
if i > 100 {
|
|
t.Fatal("the bundle never subscribed")
|
|
}
|
|
time.Sleep(20 * time.Millisecond)
|
|
}
|
|
}
|
|
|
|
// An event is taken by any answer that is not an error, and not taken by an error, which reaches the
|
|
// runtime as the bundle's own words — not as something the runtime failed to read (hq issue 276).
|
|
func TestAnEventIsTakenByAnyAnswerThatIsNotAnError(t *testing.T) {
|
|
for _, answer := range []string{"result", "bare"} {
|
|
if err := deliverTo(t, answer); err != nil {
|
|
t.Errorf("an answer %q did not take the event: %v", answer, err)
|
|
}
|
|
}
|
|
err := deliverTo(t, "error")
|
|
var refused *Refused
|
|
if !errors.As(err, &refused) || refused.Method != "mesh/event" || refused.Message != "Unexpected end of JSON input" {
|
|
t.Fatalf("an error answer was not the bundle's refusal of mesh/event: %#v", err)
|
|
}
|
|
}
|