Wrote roots-ws golang implementation.
This commit is contained in:
121
envelope/enclose.go
Normal file
121
envelope/enclose.go
Normal file
@@ -0,0 +1,121 @@
|
||||
package envelope
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// EncloseEvent creates an EVENT envelope for publishing events.
|
||||
// It wraps the provided event JSON in the format ["EVENT", event].
|
||||
func EncloseEvent(event []byte) Envelope {
|
||||
var buf bytes.Buffer
|
||||
buf.WriteString(`["EVENT",`)
|
||||
buf.Write(event)
|
||||
buf.WriteByte(']')
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
// EncloseOK creates an OK envelope acknowledging receipt of an event.
|
||||
// Format: ["OK", eventID, status, message]
|
||||
func EncloseOK(eventID string, status bool, message string) Envelope {
|
||||
var buf bytes.Buffer
|
||||
buf.WriteString(`["OK","`)
|
||||
buf.WriteString(eventID)
|
||||
buf.WriteString(`",`)
|
||||
buf.WriteString(strconv.FormatBool(status))
|
||||
buf.WriteString(`,"`)
|
||||
buf.WriteString(message)
|
||||
buf.WriteString(`"]`)
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
// EncloseReq creates a REQ envelope for subscription requests.
|
||||
// Format: ["REQ", subID, filter1, filter2, ...]
|
||||
func EncloseReq(subID string, filters [][]byte) Envelope {
|
||||
var buf bytes.Buffer
|
||||
buf.WriteString(`["REQ","`)
|
||||
buf.WriteString(subID)
|
||||
buf.WriteString(`"`)
|
||||
|
||||
for _, filter := range filters {
|
||||
buf.WriteString(`,`)
|
||||
buf.Write(filter)
|
||||
}
|
||||
|
||||
buf.WriteByte(']')
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
// EncloseSubscriptionEvent creates an EVENT envelope for delivering subscription events.
|
||||
// Format: ["EVENT", subID, event]
|
||||
func EncloseSubscriptionEvent(subID string, event []byte) Envelope {
|
||||
var buf bytes.Buffer
|
||||
buf.WriteString(`["EVENT","`)
|
||||
buf.WriteString(subID)
|
||||
buf.WriteString(`",`)
|
||||
buf.Write(event)
|
||||
buf.WriteByte(']')
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
// EncloseEOSE creates an EOSE (End of Stored Events) envelope.
|
||||
// Format: ["EOSE", subID]
|
||||
func EncloseEOSE(subID string) Envelope {
|
||||
var buf bytes.Buffer
|
||||
buf.WriteString(`["EOSE","`)
|
||||
buf.WriteString(subID)
|
||||
buf.WriteString(`"]`)
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
// EncloseClose creates a CLOSE envelope for ending a subscription.
|
||||
// Format: ["CLOSE", subID]
|
||||
func EncloseClose(subID string) Envelope {
|
||||
var buf bytes.Buffer
|
||||
buf.WriteString(`["CLOSE","`)
|
||||
buf.WriteString(subID)
|
||||
buf.WriteString(`"]`)
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
// EncloseClosed creates a CLOSED envelope for indicating a terminated subscription.
|
||||
// Format: ["CLOSED", subID, message]
|
||||
func EncloseClosed(subID string, message string) Envelope {
|
||||
var buf bytes.Buffer
|
||||
buf.WriteString(`["CLOSED","`)
|
||||
buf.WriteString(subID)
|
||||
buf.WriteString(`","`)
|
||||
buf.WriteString(message)
|
||||
buf.WriteString(`"]`)
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
// EncloseNotice creates a NOTICE envelope for responder messages.
|
||||
// Format: ["NOTICE", message]
|
||||
func EncloseNotice(message string) Envelope {
|
||||
var buf bytes.Buffer
|
||||
buf.WriteString(`["NOTICE","`)
|
||||
buf.WriteString(message)
|
||||
buf.WriteString(`"]`)
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
// EncloseAuthChallenge creates an AUTH challenge envelope.
|
||||
// Format: ["AUTH", challenge]
|
||||
func EncloseAuthChallenge(challenge string) Envelope {
|
||||
var buf bytes.Buffer
|
||||
buf.WriteString(`["AUTH","`)
|
||||
buf.WriteString(challenge)
|
||||
buf.WriteString(`"]`)
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
// EncloseAuthResponse creates an AUTH response envelope.
|
||||
// Format: ["AUTH", event]
|
||||
func EncloseAuthResponse(event []byte) Envelope {
|
||||
var buf bytes.Buffer
|
||||
buf.WriteString(`["AUTH",`)
|
||||
buf.Write(event)
|
||||
buf.WriteByte(']')
|
||||
return buf.Bytes()
|
||||
}
|
||||
289
envelope/enclose_test.go
Normal file
289
envelope/enclose_test.go
Normal file
@@ -0,0 +1,289 @@
|
||||
package envelope
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestEncloseEvent(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
event []byte
|
||||
want Envelope
|
||||
}{
|
||||
{
|
||||
name: "empty event",
|
||||
event: []byte("{}"),
|
||||
want: []byte(`["EVENT",{}]`),
|
||||
},
|
||||
{
|
||||
name: "invalid json",
|
||||
event: []byte("in[valid,]"),
|
||||
want: []byte(`["EVENT",in[valid,]]`),
|
||||
},
|
||||
{
|
||||
name: "populated event",
|
||||
event: []byte(`{"id":"abc123","kind":1,"sig":"abc123"}`),
|
||||
want: []byte(`["EVENT",{"id":"abc123","kind":1,"sig":"abc123"}]`),
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got := EncloseEvent(tc.event)
|
||||
assert.Equal(t, tc.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncloseOK(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
eventID string
|
||||
status bool
|
||||
message string
|
||||
want Envelope
|
||||
}{
|
||||
{
|
||||
name: "successful event",
|
||||
eventID: "abc123",
|
||||
status: true,
|
||||
message: "Event accepted",
|
||||
want: []byte(`["OK","abc123",true,"Event accepted"]`),
|
||||
},
|
||||
{
|
||||
name: "rejected event",
|
||||
eventID: "xyz789",
|
||||
status: false,
|
||||
message: "Invalid signature",
|
||||
want: []byte(`["OK","xyz789",false,"Invalid signature"]`),
|
||||
},
|
||||
{
|
||||
name: "empty message",
|
||||
eventID: "def456",
|
||||
status: true,
|
||||
message: "",
|
||||
want: []byte(`["OK","def456",true,""]`),
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got := EncloseOK(tc.eventID, tc.status, tc.message)
|
||||
assert.Equal(t, tc.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncloseReq(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
subID string
|
||||
filters [][]byte
|
||||
want Envelope
|
||||
}{
|
||||
{
|
||||
name: "single filter",
|
||||
subID: "sub1",
|
||||
filters: [][]byte{[]byte(`{"kinds":[1],"limit":10}`)},
|
||||
want: []byte(`["REQ","sub1",{"kinds":[1],"limit":10}]`),
|
||||
},
|
||||
{
|
||||
name: "multiple filters",
|
||||
subID: "sub2",
|
||||
filters: [][]byte{[]byte(`{"kinds":[1]}`), []byte(`{"authors":["abc"]}`)},
|
||||
want: []byte(`["REQ","sub2",{"kinds":[1]},{"authors":["abc"]}]`),
|
||||
},
|
||||
{
|
||||
name: "no filters",
|
||||
subID: "sub3",
|
||||
filters: [][]byte{},
|
||||
want: []byte(`["REQ","sub3"]`),
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got := EncloseReq(tc.subID, tc.filters)
|
||||
assert.Equal(t, tc.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncloseSubscriptionEvent(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
subID string
|
||||
event []byte
|
||||
want Envelope
|
||||
}{
|
||||
{
|
||||
name: "basic event",
|
||||
subID: "sub1",
|
||||
event: []byte(`{"id":"abc123","kind":1}`),
|
||||
want: []byte(`["EVENT","sub1",{"id":"abc123","kind":1}]`),
|
||||
},
|
||||
{
|
||||
name: "empty event",
|
||||
subID: "sub2",
|
||||
event: []byte(`{}`),
|
||||
want: []byte(`["EVENT","sub2",{}]`),
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got := EncloseSubscriptionEvent(tc.subID, tc.event)
|
||||
assert.Equal(t, tc.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncloseEOSE(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
subID string
|
||||
want Envelope
|
||||
}{
|
||||
{
|
||||
name: "valid subscription ID",
|
||||
subID: "sub1",
|
||||
want: []byte(`["EOSE","sub1"]`),
|
||||
},
|
||||
{
|
||||
name: "empty subscription ID",
|
||||
subID: "",
|
||||
want: []byte(`["EOSE",""]`),
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got := EncloseEOSE(tc.subID)
|
||||
assert.Equal(t, tc.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncloseClose(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
subID string
|
||||
want Envelope
|
||||
}{
|
||||
{
|
||||
name: "valid subscription ID",
|
||||
subID: "sub1",
|
||||
want: []byte(`["CLOSE","sub1"]`),
|
||||
},
|
||||
{
|
||||
name: "empty subscription ID",
|
||||
subID: "",
|
||||
want: []byte(`["CLOSE",""]`),
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got := EncloseClose(tc.subID)
|
||||
assert.Equal(t, tc.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncloseClosed(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
subID string
|
||||
message string
|
||||
want Envelope
|
||||
}{
|
||||
{
|
||||
name: "with message",
|
||||
subID: "sub1",
|
||||
message: "Subscription complete",
|
||||
want: []byte(`["CLOSED","sub1","Subscription complete"]`),
|
||||
},
|
||||
{
|
||||
name: "empty message",
|
||||
subID: "sub2",
|
||||
message: "",
|
||||
want: []byte(`["CLOSED","sub2",""]`),
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got := EncloseClosed(tc.subID, tc.message)
|
||||
assert.Equal(t, tc.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncloseNotice(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
message string
|
||||
want Envelope
|
||||
}{
|
||||
{
|
||||
name: "valid message",
|
||||
message: "This is a notice",
|
||||
want: []byte(`["NOTICE","This is a notice"]`),
|
||||
},
|
||||
{
|
||||
name: "empty message",
|
||||
message: "",
|
||||
want: []byte(`["NOTICE",""]`),
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got := EncloseNotice(tc.message)
|
||||
assert.Equal(t, tc.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncloseAuthChallenge(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
challenge string
|
||||
want Envelope
|
||||
}{
|
||||
{
|
||||
name: "valid challenge",
|
||||
challenge: "random-challenge-string",
|
||||
want: []byte(`["AUTH","random-challenge-string"]`),
|
||||
},
|
||||
{
|
||||
name: "empty challenge",
|
||||
challenge: "",
|
||||
want: []byte(`["AUTH",""]`),
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got := EncloseAuthChallenge(tc.challenge)
|
||||
assert.Equal(t, tc.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncloseAuthResponse(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
event []byte
|
||||
want Envelope
|
||||
}{
|
||||
{
|
||||
name: "valid event",
|
||||
event: []byte(`{"id":"abc123","kind":22242}`),
|
||||
want: []byte(`["AUTH",{"id":"abc123","kind":22242}]`),
|
||||
},
|
||||
{
|
||||
name: "empty event",
|
||||
event: []byte(`{}`),
|
||||
want: []byte(`["AUTH",{}]`),
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got := EncloseAuthResponse(tc.event)
|
||||
assert.Equal(t, tc.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
55
envelope/envelope.go
Normal file
55
envelope/envelope.go
Normal file
@@ -0,0 +1,55 @@
|
||||
// Package envelope provides types and functions for working with Nostr protocol
|
||||
// websocket messages. It defines the Envelope type representing a Nostr message
|
||||
// and offers utilities for creating, parsing, and validating standardized message
|
||||
// formats.
|
||||
package envelope
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"git.wisehodl.dev/jay/go-roots-ws/errors"
|
||||
)
|
||||
|
||||
// Envelope represents a Nostr websocket message.
|
||||
type Envelope []byte
|
||||
|
||||
// GetLabel extracts the message label from an envelope.
|
||||
// Returns the label as a string or an error if the envelope is malformed.
|
||||
func GetLabel(env Envelope) (string, error) {
|
||||
var arr []json.RawMessage
|
||||
if err := json.Unmarshal(env, &arr); err != nil {
|
||||
return "", fmt.Errorf("%w: %v", errors.InvalidJSON, err)
|
||||
}
|
||||
|
||||
if len(arr) < 1 {
|
||||
return "", fmt.Errorf("%w: empty envelope", errors.InvalidEnvelope)
|
||||
}
|
||||
|
||||
var label string
|
||||
if err := json.Unmarshal(arr[0], &label); err != nil {
|
||||
return "", fmt.Errorf("%w: label is not a string", errors.WrongFieldType)
|
||||
}
|
||||
|
||||
return label, nil
|
||||
}
|
||||
|
||||
// GetStandardLabels returns a set of standard Nostr websocket message labels
|
||||
func GetStandardLabels() map[string]struct{} {
|
||||
return map[string]struct{}{
|
||||
"EVENT": {},
|
||||
"REQ": {},
|
||||
"CLOSE": {},
|
||||
"CLOSED": {},
|
||||
"EOSE": {},
|
||||
"NOTICE": {},
|
||||
"OK": {},
|
||||
"AUTH": {},
|
||||
}
|
||||
}
|
||||
|
||||
// IsStandardLabel checks if the given label is a standard Nostr websocket message label
|
||||
func IsStandardLabel(label string) bool {
|
||||
labels := GetStandardLabels()
|
||||
_, ok := labels[label]
|
||||
return ok
|
||||
}
|
||||
110
envelope/envelope_test.go
Normal file
110
envelope/envelope_test.go
Normal file
@@ -0,0 +1,110 @@
|
||||
package envelope
|
||||
|
||||
import (
|
||||
"git.wisehodl.dev/jay/go-roots-ws/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGetLabel(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
env Envelope
|
||||
wantLabel string
|
||||
wantErr error
|
||||
wantErrText string
|
||||
}{
|
||||
{
|
||||
name: "valid envelope with EVENT label",
|
||||
env: []byte(`["EVENT",{"id":"abc123"}]`),
|
||||
wantLabel: "EVENT",
|
||||
},
|
||||
{
|
||||
name: "valid envelope with custom label",
|
||||
env: []byte(`["TEST",{"data":"value"}]`),
|
||||
wantLabel: "TEST",
|
||||
},
|
||||
{
|
||||
name: "invalid json",
|
||||
env: []byte(`invalid`),
|
||||
wantErr: errors.InvalidJSON,
|
||||
},
|
||||
{
|
||||
name: "empty array",
|
||||
env: []byte(`[]`),
|
||||
wantErr: errors.InvalidEnvelope,
|
||||
wantErrText: "empty envelope",
|
||||
},
|
||||
{
|
||||
name: "label not a string",
|
||||
env: []byte(`[123,{"id":"abc123"}]`),
|
||||
wantErr: errors.WrongFieldType,
|
||||
wantErrText: "label is not a string",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got, err := GetLabel(tc.env)
|
||||
|
||||
if tc.wantErr != nil || tc.wantErrText != "" {
|
||||
if tc.wantErr != nil {
|
||||
assert.ErrorIs(t, err, tc.wantErr)
|
||||
}
|
||||
if tc.wantErrText != "" {
|
||||
assert.ErrorContains(t, err, tc.wantErrText)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tc.wantLabel, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetStandardLabels(t *testing.T) {
|
||||
expected := map[string]struct{}{
|
||||
"EVENT": {},
|
||||
"REQ": {},
|
||||
"CLOSE": {},
|
||||
"CLOSED": {},
|
||||
"EOSE": {},
|
||||
"NOTICE": {},
|
||||
"OK": {},
|
||||
"AUTH": {},
|
||||
}
|
||||
|
||||
labels := GetStandardLabels()
|
||||
|
||||
// Check that we have the exact same number of labels
|
||||
assert.Equal(t, len(expected), len(labels))
|
||||
|
||||
// Check that all expected labels are present
|
||||
for label := range expected {
|
||||
_, exists := labels[label]
|
||||
assert.True(t, exists, "Expected standard label %s not found", label)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsStandardLabel(t *testing.T) {
|
||||
standardCases := []string{
|
||||
"EVENT", "REQ", "CLOSE", "CLOSED", "EOSE", "NOTICE", "OK", "AUTH",
|
||||
}
|
||||
|
||||
nonStandardCases := []string{
|
||||
"TEST", "CUSTOM", "event", "REQ1", "",
|
||||
}
|
||||
|
||||
for _, label := range standardCases {
|
||||
t.Run(label, func(t *testing.T) {
|
||||
assert.True(t, IsStandardLabel(label), "Label %s should be standard", label)
|
||||
})
|
||||
}
|
||||
|
||||
for _, label := range nonStandardCases {
|
||||
t.Run(label, func(t *testing.T) {
|
||||
assert.False(t, IsStandardLabel(label), "Label %s should not be standard", label)
|
||||
})
|
||||
}
|
||||
}
|
||||
324
envelope/find.go
Normal file
324
envelope/find.go
Normal file
@@ -0,0 +1,324 @@
|
||||
package envelope
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"git.wisehodl.dev/jay/go-roots-ws/errors"
|
||||
)
|
||||
|
||||
// CheckArrayLength is a helper function that ensures the JSON array has at
|
||||
// least the minimum length required
|
||||
func CheckArrayLength(arr []json.RawMessage, minLen int) error {
|
||||
if len(arr) < minLen {
|
||||
return fmt.Errorf("%w: expected %d elements, got %d", errors.InvalidEnvelope, minLen, len(arr))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CheckLabel is a helper function that verifies that the envelope label
|
||||
// matches the expected one
|
||||
func CheckLabel(got, want string) error {
|
||||
if got != want {
|
||||
return fmt.Errorf("%w: expected %s, got %s", errors.WrongEnvelopeLabel, want, got)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ParseElement is a helper function that unmarshals an array element into the
|
||||
// provided value
|
||||
func ParseElement(element json.RawMessage, value interface{}, position string) error {
|
||||
if err := json.Unmarshal(element, value); err != nil {
|
||||
return fmt.Errorf("%w: %s is not the expected type", errors.WrongFieldType, position)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// FindEvent extracts an event from an EVENT envelope with no subscription ID.
|
||||
// Expected Format: ["EVENT", event]
|
||||
func FindEvent(env Envelope) ([]byte, error) {
|
||||
var arr []json.RawMessage
|
||||
if err := json.Unmarshal(env, &arr); err != nil {
|
||||
return nil, fmt.Errorf("%w: %v", errors.InvalidJSON, err)
|
||||
}
|
||||
|
||||
if err := CheckArrayLength(arr, 2); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var label string
|
||||
if err := ParseElement(arr[0], &label, "envelope label"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := CheckLabel(label, "EVENT"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return arr[1], nil
|
||||
}
|
||||
|
||||
// FindSubscriptionEvent extracts an event and subscription ID from an EVENT envelope.
|
||||
// Expected Format: ["EVENT", subID, event]
|
||||
func FindSubscriptionEvent(env Envelope) (subID string, event []byte, err error) {
|
||||
var arr []json.RawMessage
|
||||
if err = json.Unmarshal(env, &arr); err != nil {
|
||||
return "", nil, fmt.Errorf("%w: %v", errors.InvalidJSON, err)
|
||||
}
|
||||
|
||||
if err = CheckArrayLength(arr, 3); err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
var label string
|
||||
if err := ParseElement(arr[0], &label, "envelope label"); err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
if err = CheckLabel(label, "EVENT"); err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
if err = ParseElement(arr[1], &subID, "subscription ID"); err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
return subID, arr[2], nil
|
||||
}
|
||||
|
||||
// FindOK extracts eventID, status, and message from an OK envelope.
|
||||
// Expected Format: ["OK", eventID, status, message]
|
||||
func FindOK(env Envelope) (eventID string, status bool, message string, err error) {
|
||||
var arr []json.RawMessage
|
||||
if err = json.Unmarshal(env, &arr); err != nil {
|
||||
return "", false, "", fmt.Errorf("%w: %v", errors.InvalidJSON, err)
|
||||
}
|
||||
|
||||
if err = CheckArrayLength(arr, 4); err != nil {
|
||||
return "", false, "", err
|
||||
}
|
||||
|
||||
var label string
|
||||
if err := ParseElement(arr[0], &label, "envelope label"); err != nil {
|
||||
return "", false, "", err
|
||||
}
|
||||
|
||||
if err = CheckLabel(label, "OK"); err != nil {
|
||||
return "", false, "", err
|
||||
}
|
||||
|
||||
if err = ParseElement(arr[1], &eventID, "event ID"); err != nil {
|
||||
return "", false, "", err
|
||||
}
|
||||
|
||||
if err = ParseElement(arr[2], &status, "status"); err != nil {
|
||||
return "", false, "", err
|
||||
}
|
||||
|
||||
if err = ParseElement(arr[3], &message, "message"); err != nil {
|
||||
return "", false, "", err
|
||||
}
|
||||
|
||||
return eventID, status, message, nil
|
||||
}
|
||||
|
||||
// FindReq extracts subscription ID and filters from a REQ envelope.
|
||||
// Expected Format: ["REQ", subID, filter1, filter2, ...]
|
||||
func FindReq(env Envelope) (subID string, filters [][]byte, err error) {
|
||||
var arr []json.RawMessage
|
||||
if err = json.Unmarshal(env, &arr); err != nil {
|
||||
return "", nil, fmt.Errorf("%w: %v", errors.InvalidJSON, err)
|
||||
}
|
||||
|
||||
if err = CheckArrayLength(arr, 2); err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
var label string
|
||||
if err := ParseElement(arr[0], &label, "envelope label"); err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
if err = CheckLabel(label, "REQ"); err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
if err = ParseElement(arr[1], &subID, "subscription ID"); err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
filters = make([][]byte, 0, len(arr)-2)
|
||||
for i := 2; i < len(arr); i++ {
|
||||
filters = append(filters, arr[i])
|
||||
}
|
||||
|
||||
return subID, filters, nil
|
||||
}
|
||||
|
||||
// FindEOSE extracts subscription ID from an EOSE envelope.
|
||||
// Expected Format: ["EOSE", subID]
|
||||
func FindEOSE(env Envelope) (subID string, err error) {
|
||||
var arr []json.RawMessage
|
||||
if err = json.Unmarshal(env, &arr); err != nil {
|
||||
return "", fmt.Errorf("%w: %v", errors.InvalidJSON, err)
|
||||
}
|
||||
|
||||
if err = CheckArrayLength(arr, 2); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var label string
|
||||
if err := ParseElement(arr[0], &label, "envelope label"); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if err = CheckLabel(label, "EOSE"); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if err = ParseElement(arr[1], &subID, "subscription ID"); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return subID, nil
|
||||
}
|
||||
|
||||
// FindClose extracts subscription ID from a CLOSE envelope.
|
||||
// Expected Format: ["CLOSE", subID]
|
||||
func FindClose(env Envelope) (subID string, err error) {
|
||||
var arr []json.RawMessage
|
||||
if err = json.Unmarshal(env, &arr); err != nil {
|
||||
return "", fmt.Errorf("%w: %v", errors.InvalidJSON, err)
|
||||
}
|
||||
|
||||
if err = CheckArrayLength(arr, 2); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var label string
|
||||
if err := ParseElement(arr[0], &label, "envelope label"); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if err = CheckLabel(label, "CLOSE"); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if err = ParseElement(arr[1], &subID, "subscription ID"); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return subID, nil
|
||||
}
|
||||
|
||||
// FindClosed extracts subscription ID and message from a CLOSED envelope.
|
||||
// Expected Format: ["CLOSED", subID, message]
|
||||
func FindClosed(env Envelope) (subID string, message string, err error) {
|
||||
var arr []json.RawMessage
|
||||
if err = json.Unmarshal(env, &arr); err != nil {
|
||||
return "", "", fmt.Errorf("%w: %v", errors.InvalidJSON, err)
|
||||
}
|
||||
|
||||
if err = CheckArrayLength(arr, 3); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
var label string
|
||||
if err := ParseElement(arr[0], &label, "envelope label"); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
if err = CheckLabel(label, "CLOSED"); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
if err = ParseElement(arr[1], &subID, "subscription ID"); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
if err = ParseElement(arr[2], &message, "message"); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
return subID, message, nil
|
||||
}
|
||||
|
||||
// FindNotice extracts message from a NOTICE envelope.
|
||||
// Expected Format: ["NOTICE", message]
|
||||
func FindNotice(env Envelope) (message string, err error) {
|
||||
var arr []json.RawMessage
|
||||
if err = json.Unmarshal(env, &arr); err != nil {
|
||||
return "", fmt.Errorf("%w: %v", errors.InvalidJSON, err)
|
||||
}
|
||||
|
||||
if err = CheckArrayLength(arr, 2); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var label string
|
||||
if err := ParseElement(arr[0], &label, "envelope label"); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if err = CheckLabel(label, "NOTICE"); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if err = ParseElement(arr[1], &message, "message"); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return message, nil
|
||||
}
|
||||
|
||||
// FindAuthChallenge extracts challenge from an AUTH challenge envelope.
|
||||
// Expected Format: ["AUTH", challenge]
|
||||
func FindAuthChallenge(env Envelope) (challenge string, err error) {
|
||||
var arr []json.RawMessage
|
||||
if err = json.Unmarshal(env, &arr); err != nil {
|
||||
return "", fmt.Errorf("%w: %v", errors.InvalidJSON, err)
|
||||
}
|
||||
|
||||
if err = CheckArrayLength(arr, 2); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var label string
|
||||
if err := ParseElement(arr[0], &label, "envelope label"); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if err = CheckLabel(label, "AUTH"); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Check if the second element is a string (AUTH challenge)
|
||||
if err = ParseElement(arr[1], &challenge, "challenge"); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return challenge, nil
|
||||
}
|
||||
|
||||
// FindAuthResponse extracts event from an AUTH response envelope.
|
||||
// Expected Format: ["AUTH", event]
|
||||
func FindAuthResponse(env Envelope) (event []byte, err error) {
|
||||
var arr []json.RawMessage
|
||||
if err = json.Unmarshal(env, &arr); err != nil {
|
||||
return nil, fmt.Errorf("%w: %v", errors.InvalidJSON, err)
|
||||
}
|
||||
|
||||
if err = CheckArrayLength(arr, 2); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var label string
|
||||
if err := ParseElement(arr[0], &label, "envelope label"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = CheckLabel(label, "AUTH"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return arr[1], nil
|
||||
}
|
||||
630
envelope/find_test.go
Normal file
630
envelope/find_test.go
Normal file
@@ -0,0 +1,630 @@
|
||||
package envelope
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.wisehodl.dev/jay/go-roots-ws/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestFindEvent(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
env Envelope
|
||||
wantEvent []byte
|
||||
wantErr error
|
||||
wantErrText string
|
||||
}{
|
||||
{
|
||||
name: "valid event",
|
||||
env: []byte(`["EVENT",{"id":"abc123","kind":1}]`),
|
||||
wantEvent: []byte(`{"id":"abc123","kind":1}`),
|
||||
},
|
||||
{
|
||||
name: "wrong label",
|
||||
env: []byte(`["REQ",{"id":"abc123","kind":1}]`),
|
||||
wantErr: errors.WrongEnvelopeLabel,
|
||||
wantErrText: "expected EVENT, got REQ",
|
||||
},
|
||||
{
|
||||
name: "invalid json",
|
||||
env: []byte(`invalid`),
|
||||
wantErr: errors.InvalidJSON,
|
||||
},
|
||||
{
|
||||
name: "missing elements",
|
||||
env: []byte(`["EVENT"]`),
|
||||
wantErr: errors.InvalidEnvelope,
|
||||
wantErrText: "expected 2 elements, got 1",
|
||||
},
|
||||
{
|
||||
name: "extraneous elements",
|
||||
env: []byte(`["EVENT",{"id":"abc123"},"extra"]`),
|
||||
wantEvent: []byte(`{"id":"abc123"}`),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got, err := FindEvent(tc.env)
|
||||
|
||||
if tc.wantErr != nil || tc.wantErrText != "" {
|
||||
if tc.wantErr != nil {
|
||||
assert.ErrorIs(t, err, tc.wantErr)
|
||||
}
|
||||
|
||||
if tc.wantErrText != "" {
|
||||
assert.ErrorContains(t, err, tc.wantErrText)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tc.wantEvent, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindSubscriptionEvent(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
env Envelope
|
||||
wantSubID string
|
||||
wantEvent []byte
|
||||
wantErr error
|
||||
wantErrText string
|
||||
}{
|
||||
{
|
||||
name: "valid event",
|
||||
env: []byte(`["EVENT","sub1",{"id":"abc123","kind":1}]`),
|
||||
wantSubID: "sub1",
|
||||
wantEvent: []byte(`{"id":"abc123","kind":1}`),
|
||||
},
|
||||
{
|
||||
name: "wrong label",
|
||||
env: []byte(`["REQ","sub1",{"id":"abc123","kind":1}]`),
|
||||
wantErr: errors.WrongEnvelopeLabel,
|
||||
wantErrText: "expected EVENT, got REQ",
|
||||
},
|
||||
{
|
||||
name: "invalid json",
|
||||
env: []byte(`invalid`),
|
||||
wantErr: errors.InvalidJSON,
|
||||
},
|
||||
{
|
||||
name: "missing elements",
|
||||
env: []byte(`["EVENT","sub1"]`),
|
||||
wantErr: errors.InvalidEnvelope,
|
||||
wantErrText: "expected 3 elements, got 2",
|
||||
},
|
||||
{
|
||||
name: "extraneous elements",
|
||||
env: []byte(`["EVENT","sub1",{"id":"abc123"},"extra"]`),
|
||||
wantSubID: "sub1",
|
||||
wantEvent: []byte(`{"id":"abc123"}`),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
gotSubID, gotEvent, err := FindSubscriptionEvent(tc.env)
|
||||
|
||||
if tc.wantErr != nil || tc.wantErrText != "" {
|
||||
if tc.wantErr != nil {
|
||||
assert.ErrorIs(t, err, tc.wantErr)
|
||||
}
|
||||
|
||||
if tc.wantErrText != "" {
|
||||
assert.ErrorContains(t, err, tc.wantErrText)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tc.wantSubID, gotSubID)
|
||||
assert.Equal(t, tc.wantEvent, gotEvent)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindOK(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
env Envelope
|
||||
wantEventID string
|
||||
wantStatus bool
|
||||
wantMessage string
|
||||
wantErr error
|
||||
wantErrText string
|
||||
}{
|
||||
{
|
||||
name: "accepted event",
|
||||
env: []byte(`["OK","abc123",true,"Event accepted"]`),
|
||||
wantEventID: "abc123",
|
||||
wantStatus: true,
|
||||
wantMessage: "Event accepted",
|
||||
},
|
||||
{
|
||||
name: "rejected event",
|
||||
env: []byte(`["OK","xyz789",false,"Invalid signature"]`),
|
||||
wantEventID: "xyz789",
|
||||
wantStatus: false,
|
||||
wantMessage: "Invalid signature",
|
||||
},
|
||||
{
|
||||
name: "wrong status type",
|
||||
env: []byte(`["OK","abc123","ok","Event accepted"]`),
|
||||
wantErr: errors.WrongFieldType,
|
||||
wantErrText: "status is not the expected type",
|
||||
},
|
||||
{
|
||||
name: "wrong label",
|
||||
env: []byte(`["EVENT","abc123",true,"Event accepted"]`),
|
||||
wantErr: errors.WrongEnvelopeLabel,
|
||||
wantErrText: "expected OK, got EVENT",
|
||||
},
|
||||
{
|
||||
name: "invalid json",
|
||||
env: []byte(`invalid`),
|
||||
wantErr: errors.InvalidJSON,
|
||||
},
|
||||
{
|
||||
name: "missing elements",
|
||||
env: []byte(`["OK","abc123",true]`),
|
||||
wantErr: errors.InvalidEnvelope,
|
||||
wantErrText: "expected 4 elements, got 3",
|
||||
},
|
||||
{
|
||||
name: "extraneous elements",
|
||||
env: []byte(`["OK","abc123",true,"Event accepted","extra"]`),
|
||||
wantEventID: "abc123",
|
||||
wantStatus: true,
|
||||
wantMessage: "Event accepted",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
gotEventID, gotStatus, gotMessage, err := FindOK(tc.env)
|
||||
|
||||
if tc.wantErr != nil || tc.wantErrText != "" {
|
||||
if tc.wantErr != nil {
|
||||
assert.ErrorIs(t, err, tc.wantErr)
|
||||
}
|
||||
|
||||
if tc.wantErrText != "" {
|
||||
assert.ErrorContains(t, err, tc.wantErrText)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tc.wantEventID, gotEventID)
|
||||
assert.Equal(t, tc.wantStatus, gotStatus)
|
||||
assert.Equal(t, tc.wantMessage, gotMessage)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindReq(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
env Envelope
|
||||
wantSubID string
|
||||
wantFilters [][]byte
|
||||
wantErr error
|
||||
wantErrText string
|
||||
}{
|
||||
{
|
||||
name: "single filter",
|
||||
env: []byte(`["REQ","sub1",{"kinds":[1],"limit":10}]`),
|
||||
wantSubID: "sub1",
|
||||
wantFilters: [][]byte{[]byte(`{"kinds":[1],"limit":10}`)},
|
||||
},
|
||||
{
|
||||
name: "multiple filters",
|
||||
env: []byte(`["REQ","sub2",{"kinds":[1]},{"authors":["abc"]}]`),
|
||||
wantSubID: "sub2",
|
||||
wantFilters: [][]byte{
|
||||
[]byte(`{"kinds":[1]}`),
|
||||
[]byte(`{"authors":["abc"]}`),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no filters",
|
||||
env: []byte(`["REQ","sub3"]`),
|
||||
wantSubID: "sub3",
|
||||
wantFilters: [][]byte{},
|
||||
},
|
||||
{
|
||||
name: "wrong label",
|
||||
env: []byte(`["EVENT","sub1",{"kinds":[1],"limit":10}]`),
|
||||
wantErr: errors.WrongEnvelopeLabel,
|
||||
wantErrText: "expected REQ, got EVENT",
|
||||
},
|
||||
{
|
||||
name: "invalid json",
|
||||
env: []byte(`invalid`),
|
||||
wantErr: errors.InvalidJSON,
|
||||
},
|
||||
{
|
||||
name: "missing elements",
|
||||
env: []byte(`["REQ"]`),
|
||||
wantErr: errors.InvalidEnvelope,
|
||||
wantErrText: "expected 2 elements, got 1",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
gotSubID, gotFilters, err := FindReq(tc.env)
|
||||
|
||||
if tc.wantErr != nil || tc.wantErrText != "" {
|
||||
if tc.wantErr != nil {
|
||||
assert.ErrorIs(t, err, tc.wantErr)
|
||||
}
|
||||
|
||||
if tc.wantErrText != "" {
|
||||
assert.ErrorContains(t, err, tc.wantErrText)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tc.wantSubID, gotSubID)
|
||||
assert.Equal(t, tc.wantFilters, gotFilters)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindEOSE(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
env Envelope
|
||||
wantSubID string
|
||||
wantErr error
|
||||
wantErrText string
|
||||
}{
|
||||
{
|
||||
name: "valid EOSE",
|
||||
env: []byte(`["EOSE","sub1"]`),
|
||||
wantSubID: "sub1",
|
||||
},
|
||||
{
|
||||
name: "wrong label",
|
||||
env: []byte(`["EVENT","sub1"]`),
|
||||
wantErr: errors.WrongEnvelopeLabel,
|
||||
wantErrText: "expected EOSE, got EVENT",
|
||||
},
|
||||
{
|
||||
name: "invalid json",
|
||||
env: []byte(`invalid`),
|
||||
wantErr: errors.InvalidJSON,
|
||||
},
|
||||
{
|
||||
name: "missing elements",
|
||||
env: []byte(`["EOSE"]`),
|
||||
wantErr: errors.InvalidEnvelope,
|
||||
wantErrText: "expected 2 elements, got 1",
|
||||
},
|
||||
{
|
||||
name: "extraneous elements",
|
||||
env: []byte(`["EOSE","sub1","extra"]`),
|
||||
wantSubID: "sub1",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
gotSubID, err := FindEOSE(tc.env)
|
||||
|
||||
if tc.wantErr != nil || tc.wantErrText != "" {
|
||||
if tc.wantErr != nil {
|
||||
assert.ErrorIs(t, err, tc.wantErr)
|
||||
}
|
||||
|
||||
if tc.wantErrText != "" {
|
||||
assert.ErrorContains(t, err, tc.wantErrText)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tc.wantSubID, gotSubID)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindClose(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
env Envelope
|
||||
wantSubID string
|
||||
wantErr error
|
||||
wantErrText string
|
||||
}{
|
||||
{
|
||||
name: "valid CLOSE",
|
||||
env: []byte(`["CLOSE","sub1"]`),
|
||||
wantSubID: "sub1",
|
||||
},
|
||||
{
|
||||
name: "wrong label",
|
||||
env: []byte(`["EVENT","sub1"]`),
|
||||
wantErr: errors.WrongEnvelopeLabel,
|
||||
wantErrText: "expected CLOSE, got EVENT",
|
||||
},
|
||||
{
|
||||
name: "invalid json",
|
||||
env: []byte(`invalid`),
|
||||
wantErr: errors.InvalidJSON,
|
||||
},
|
||||
{
|
||||
name: "missing elements",
|
||||
env: []byte(`["CLOSE"]`),
|
||||
wantErr: errors.InvalidEnvelope,
|
||||
wantErrText: "expected 2 elements, got 1",
|
||||
},
|
||||
{
|
||||
name: "extraneous elements",
|
||||
env: []byte(`["CLOSE","sub1","extra"]`),
|
||||
wantSubID: "sub1",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
gotSubID, err := FindClose(tc.env)
|
||||
|
||||
if tc.wantErr != nil || tc.wantErrText != "" {
|
||||
if tc.wantErr != nil {
|
||||
assert.ErrorIs(t, err, tc.wantErr)
|
||||
}
|
||||
|
||||
if tc.wantErrText != "" {
|
||||
assert.ErrorContains(t, err, tc.wantErrText)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tc.wantSubID, gotSubID)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindClosed(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
env Envelope
|
||||
wantSubID string
|
||||
wantMessage string
|
||||
wantErr error
|
||||
wantErrText string
|
||||
}{
|
||||
{
|
||||
name: "valid CLOSED",
|
||||
env: []byte(`["CLOSED","sub1","Subscription complete"]`),
|
||||
wantSubID: "sub1",
|
||||
wantMessage: "Subscription complete",
|
||||
},
|
||||
{
|
||||
name: "wrong label",
|
||||
env: []byte(`["EVENT","sub1","Subscription complete"]`),
|
||||
wantErr: errors.WrongEnvelopeLabel,
|
||||
wantErrText: "expected CLOSED, got EVENT",
|
||||
},
|
||||
{
|
||||
name: "invalid json",
|
||||
env: []byte(`invalid`),
|
||||
wantErr: errors.InvalidJSON,
|
||||
},
|
||||
{
|
||||
name: "missing elements",
|
||||
env: []byte(`["CLOSED","sub1"]`),
|
||||
wantErr: errors.InvalidEnvelope,
|
||||
wantErrText: "expected 3 elements, got 2",
|
||||
},
|
||||
{
|
||||
name: "extraneous elements",
|
||||
env: []byte(`["CLOSED","sub1","Subscription complete","extra"]`),
|
||||
wantSubID: "sub1",
|
||||
wantMessage: "Subscription complete",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
gotSubID, gotMessage, err := FindClosed(tc.env)
|
||||
|
||||
if tc.wantErr != nil || tc.wantErrText != "" {
|
||||
if tc.wantErr != nil {
|
||||
assert.ErrorIs(t, err, tc.wantErr)
|
||||
}
|
||||
|
||||
if tc.wantErrText != "" {
|
||||
assert.ErrorContains(t, err, tc.wantErrText)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tc.wantSubID, gotSubID)
|
||||
assert.Equal(t, tc.wantMessage, gotMessage)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindNotice(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
env Envelope
|
||||
wantMessage string
|
||||
wantErr error
|
||||
wantErrText string
|
||||
}{
|
||||
{
|
||||
name: "valid NOTICE",
|
||||
env: []byte(`["NOTICE","This is a notice"]`),
|
||||
wantMessage: "This is a notice",
|
||||
},
|
||||
{
|
||||
name: "wrong label",
|
||||
env: []byte(`["EVENT","This is a notice"]`),
|
||||
wantErr: errors.WrongEnvelopeLabel,
|
||||
wantErrText: "expected NOTICE, got EVENT",
|
||||
},
|
||||
{
|
||||
name: "invalid json",
|
||||
env: []byte(`invalid`),
|
||||
wantErr: errors.InvalidJSON,
|
||||
},
|
||||
{
|
||||
name: "missing elements",
|
||||
env: []byte(`["NOTICE"]`),
|
||||
wantErr: errors.InvalidEnvelope,
|
||||
wantErrText: "expected 2 elements, got 1",
|
||||
},
|
||||
{
|
||||
name: "extraneous elements",
|
||||
env: []byte(`["NOTICE","This is a notice","extra"]`),
|
||||
wantMessage: "This is a notice",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
gotMessage, err := FindNotice(tc.env)
|
||||
|
||||
if tc.wantErr != nil || tc.wantErrText != "" {
|
||||
if tc.wantErr != nil {
|
||||
assert.ErrorIs(t, err, tc.wantErr)
|
||||
}
|
||||
|
||||
if tc.wantErrText != "" {
|
||||
assert.ErrorContains(t, err, tc.wantErrText)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tc.wantMessage, gotMessage)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindAuthChallenge(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
env Envelope
|
||||
wantChallenge string
|
||||
wantErr error
|
||||
wantErrText string
|
||||
}{
|
||||
{
|
||||
name: "valid AUTH challenge",
|
||||
env: []byte(`["AUTH","random-challenge-string"]`),
|
||||
wantChallenge: "random-challenge-string",
|
||||
},
|
||||
{
|
||||
name: "wrong label",
|
||||
env: []byte(`["EVENT","random-challenge-string"]`),
|
||||
wantErr: errors.WrongEnvelopeLabel,
|
||||
wantErrText: "expected AUTH, got EVENT",
|
||||
},
|
||||
{
|
||||
name: "invalid json",
|
||||
env: []byte(`invalid`),
|
||||
wantErr: errors.InvalidJSON,
|
||||
},
|
||||
{
|
||||
name: "missing elements",
|
||||
env: []byte(`["AUTH"]`),
|
||||
wantErr: errors.InvalidEnvelope,
|
||||
wantErrText: "expected 2 elements, got 1",
|
||||
},
|
||||
{
|
||||
name: "extraneous elements",
|
||||
env: []byte(`["AUTH","random-challenge-string","extra"]`),
|
||||
wantChallenge: "random-challenge-string",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
gotChallenge, err := FindAuthChallenge(tc.env)
|
||||
|
||||
if tc.wantErr != nil || tc.wantErrText != "" {
|
||||
if tc.wantErr != nil {
|
||||
assert.ErrorIs(t, err, tc.wantErr)
|
||||
}
|
||||
|
||||
if tc.wantErrText != "" {
|
||||
assert.ErrorContains(t, err, tc.wantErrText)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tc.wantChallenge, gotChallenge)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindAuthResponse(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
env Envelope
|
||||
wantEvent []byte
|
||||
wantErr error
|
||||
wantErrText string
|
||||
}{
|
||||
{
|
||||
name: "valid AUTH response",
|
||||
env: []byte(`["AUTH",{"id":"abc123","kind":22242}]`),
|
||||
wantEvent: []byte(`{"id":"abc123","kind":22242}`),
|
||||
},
|
||||
{
|
||||
name: "wrong label",
|
||||
env: []byte(`["EVENT",{"id":"abc123","kind":22242}]`),
|
||||
wantErr: errors.WrongEnvelopeLabel,
|
||||
wantErrText: "expected AUTH, got EVENT",
|
||||
},
|
||||
{
|
||||
name: "invalid json",
|
||||
env: []byte(`invalid`),
|
||||
wantErr: errors.InvalidJSON,
|
||||
},
|
||||
{
|
||||
name: "missing elements",
|
||||
env: []byte(`["AUTH"]`),
|
||||
wantErr: errors.InvalidEnvelope,
|
||||
wantErrText: "expected 2 elements, got 1",
|
||||
},
|
||||
{
|
||||
name: "extraneous elements",
|
||||
env: []byte(`["AUTH",{"id":"abc123","kind":22242},"extra"]`),
|
||||
wantEvent: []byte(`{"id":"abc123","kind":22242}`),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
gotEvent, err := FindAuthResponse(tc.env)
|
||||
|
||||
if tc.wantErr != nil || tc.wantErrText != "" {
|
||||
if tc.wantErr != nil {
|
||||
assert.ErrorIs(t, err, tc.wantErr)
|
||||
}
|
||||
|
||||
if tc.wantErrText != "" {
|
||||
assert.ErrorContains(t, err, tc.wantErrText)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tc.wantEvent, gotEvent)
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user