Files

96 lines
3.3 KiB
Go

package tentacle
import (
"bufio"
"bytes"
"errors"
"io"
"strings"
"testing"
)
func TestFramingBoundsAndRequiresNewline(t *testing.T) {
for _, testCase := range []struct {
name string
input string
want error
}{
{"valid", strings.Repeat("x", MaxFrameBytes) + "\n", nil},
{"oversized", strings.Repeat("x", MaxFrameBytes+1) + "\n", ErrLimit},
{"unterminated", `{}`, io.ErrUnexpectedEOF},
{"empty", "", io.EOF},
} {
t.Run(testCase.name, func(t *testing.T) {
_, err := read_frame(bufio.NewReaderSize(strings.NewReader(testCase.input), 32))
if !errors.Is(err, testCase.want) {
t.Fatalf("got %v, want %v", err, testCase.want)
}
})
}
}
func TestResponseValidation(t *testing.T) {
for _, valid := range []string{
`{"jsonrpc":"2.0","id":"1","result":null}`,
`{"jsonrpc":"2.0","id":"1","result":{"text":"é猫😀é"}}`,
`{"jsonrpc":"2.0","id":"1","error":{"code":-32601,"message":"Unknown method"}}`,
} {
if _, err := decode_response([]byte(valid)); err != nil {
t.Errorf("valid response rejected: %v", err)
}
}
for _, invalid := range []string{
`[]`, `null`, `{}`, `{"jsonrpc":"2.0","id":"1"}`,
`{"jsonrpc":"2.0","id":"1","result":null,"error":null}`,
`{"jsonrpc":"2.0","id":"1","error":null}`,
`{"jsonrpc":"2.0","id":1,"result":null}`,
`{"jsonrpc":"1.0","id":"1","result":null}`,
`{"jsonrpc":"2.0","id":"1","id":"2","result":null}`,
`{"jsonrpc":"2.0","id":"1","result":{"x":0,"x":1}}`,
`{"jsonrpc":"2.0","id":"1","result":null,"extra":1}`,
`{"jsonrpc":"2.0","id":"1","error":{"message":"missing code"}}`,
`{"jsonrpc":"2.0","id":"1","error":{"code":null,"message":"null code"}}`,
`{"jsonrpc":"2.0","id":"1","error":{"code":1,"message":null}}`,
`{"jsonrpc":"2.0","id":"1","result":"` + string([]byte{0xff}) + `"}`,
`{"jsonrpc":"2.0","id":"1","result":` + strings.Repeat("[", 40) + "0" + strings.Repeat("]", 40) + `}`,
`{"jsonrpc":"2.0","id":"1","result":null} {}`,
} {
if _, err := decode_response([]byte(invalid)); err == nil {
t.Errorf("invalid response accepted: %q", invalid)
}
}
if strings.Contains((&RPCError{Code: 1, Message: "private text"}).Error(), "private") {
t.Fatal("remote text leaked through Error")
}
}
func FuzzResponseDecoder(f *testing.F) {
f.Add([]byte(`{"jsonrpc":"2.0","id":"1","result":null}`))
f.Add([]byte(`{"jsonrpc":"2.0","id":"1","error":{"code":1,"message":"bad"}}`))
f.Fuzz(func(t *testing.T, data []byte) {
if len(data) > MaxFrameBytes {
t.Skip()
}
_, _ = decode_response(data)
_, _ = read_frame(bufio.NewReader(bytes.NewReader(data)))
})
}
func TestSettingsRegistrationRequiresOneInteractiveCommand(t *testing.T) {
command := Command{ID: "settings", Title: "Settings", Settings: true, Interaction: true, Presentation: true}
registration := Registration{Protocol: Version{Major: 1}, Session: "test", Commands: []Command{command}}
if err := validate_registration(registration, "test"); err != nil {
t.Fatal(err)
}
registration.Commands[0].Interaction = false
if err := validate_registration(registration, "test"); err == nil {
t.Fatal("accepted a settings handler without interaction")
}
registration.Commands[0] = command
command.ID = "other-settings"
registration.Commands = append(registration.Commands, command)
if err := validate_registration(registration, "test"); err == nil {
t.Fatal("accepted two settings entry points")
}
}