123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621 |
- package httpexpect
- import (
- "encoding/json"
- "errors"
- "fmt"
- "github.com/gorilla/websocket"
- )
- // WebsocketMessage provides methods to inspect message read from WebSocket connection.
- type WebsocketMessage struct {
- noCopy noCopy
- chain *chain
- typ int
- content []byte
- closeCode int
- }
- // NewWebsocketMessage returns a new WebsocketMessage instance.
- //
- // If reporter is nil, the function panics.
- // Content may be nil.
- //
- // Example:
- //
- // m := NewWebsocketMessage(t, websocket.TextMessage, []byte("content"), 0)
- // m.TextMessage()
- func NewWebsocketMessage(
- reporter Reporter, typ int, content []byte, closeCode ...int,
- ) *WebsocketMessage {
- return newWebsocketMessage(
- newChainWithDefaults("WebsocketMessage()", reporter),
- typ,
- content,
- closeCode...,
- )
- }
- // NewWebsocketMessageC returns a new WebsocketMessage instance with config.
- //
- // Requirements for config are same as for WithConfig function.
- // Content may be nil.
- //
- // Example:
- //
- // m := NewWebsocketMessageC(config, websocket.TextMessage, []byte("content"), 0)
- // m.TextMessage()
- func NewWebsocketMessageC(
- config Config, typ int, content []byte, closeCode ...int,
- ) *WebsocketMessage {
- return newWebsocketMessage(
- newChainWithConfig("WebsocketMessage()", config.withDefaults()),
- typ,
- content,
- closeCode...,
- )
- }
- func newWebsocketMessage(
- parent *chain, typ int, content []byte, closeCode ...int,
- ) *WebsocketMessage {
- wm := newEmptyWebsocketMessage(parent)
- opChain := wm.chain.enter("")
- defer opChain.leave()
- wm.typ = typ
- wm.content = content
- if len(closeCode) > 1 {
- opChain.fail(AssertionFailure{
- Type: AssertUsage,
- Errors: []error{
- errors.New("unexpected multiple closeCode arguments"),
- },
- })
- return wm
- }
- if len(closeCode) != 0 {
- wm.closeCode = closeCode[0]
- }
- return wm
- }
- func newEmptyWebsocketMessage(parent *chain) *WebsocketMessage {
- return &WebsocketMessage{
- chain: parent.clone(),
- }
- }
- // Raw returns underlying type, content and close code of WebSocket message.
- // Theses values are originally read from WebSocket connection.
- func (wm *WebsocketMessage) Raw() (typ int, content []byte, closeCode int) {
- return wm.typ, wm.content, wm.closeCode
- }
- // Alias is similar to Value.Alias.
- func (wm *WebsocketMessage) Alias(name string) *WebsocketMessage {
- opChain := wm.chain.enter("Alias(%q)", name)
- defer opChain.leave()
- wm.chain.setAlias(name)
- return wm
- }
- // CloseMessage is a shorthand for m.Type(websocket.CloseMessage).
- func (wm *WebsocketMessage) CloseMessage() *WebsocketMessage {
- opChain := wm.chain.enter("CloseMessage()")
- defer opChain.leave()
- wm.checkType(opChain, websocket.CloseMessage)
- return wm
- }
- // NotCloseMessage is a shorthand for m.NotType(websocket.CloseMessage).
- func (wm *WebsocketMessage) NotCloseMessage() *WebsocketMessage {
- opChain := wm.chain.enter("NotCloseMessage()")
- defer opChain.leave()
- wm.checkNotType(opChain, websocket.CloseMessage)
- return wm
- }
- // BinaryMessage is a shorthand for m.Type(websocket.BinaryMessage).
- func (wm *WebsocketMessage) BinaryMessage() *WebsocketMessage {
- opChain := wm.chain.enter("BinaryMessage()")
- defer opChain.leave()
- wm.checkType(opChain, websocket.BinaryMessage)
- return wm
- }
- // NotBinaryMessage is a shorthand for m.NotType(websocket.BinaryMessage).
- func (wm *WebsocketMessage) NotBinaryMessage() *WebsocketMessage {
- opChain := wm.chain.enter("NotBinaryMessage()")
- defer opChain.leave()
- wm.checkNotType(opChain, websocket.BinaryMessage)
- return wm
- }
- // TextMessage is a shorthand for m.Type(websocket.TextMessage).
- func (wm *WebsocketMessage) TextMessage() *WebsocketMessage {
- opChain := wm.chain.enter("TextMessage()")
- defer opChain.leave()
- wm.checkType(opChain, websocket.TextMessage)
- return wm
- }
- // NotTextMessage is a shorthand for m.NotType(websocket.TextMessage).
- func (wm *WebsocketMessage) NotTextMessage() *WebsocketMessage {
- opChain := wm.chain.enter("NotTextMessage()")
- defer opChain.leave()
- wm.checkNotType(opChain, websocket.TextMessage)
- return wm
- }
- // Type succeeds if WebSocket message type is one of the given.
- //
- // WebSocket message types are defined in RFC 6455, section 11.8.
- // See also https://godoc.org/github.com/gorilla/websocket#pkg-constants
- //
- // Example:
- //
- // msg := conn.Expect()
- // msg.Type(websocket.TextMessage, websocket.BinaryMessage)
- func (wm *WebsocketMessage) Type(types ...int) *WebsocketMessage {
- opChain := wm.chain.enter("Type()")
- defer opChain.leave()
- wm.checkType(opChain, types...)
- return wm
- }
- // NotType succeeds if WebSocket message type is none of the given.
- //
- // WebSocket message types are defined in RFC 6455, section 11.8.
- // See also https://godoc.org/github.com/gorilla/websocket#pkg-constants
- //
- // Example:
- //
- // msg := conn.Expect()
- // msg.NotType(websocket.CloseMessage, websocket.BinaryMessage)
- func (wm *WebsocketMessage) NotType(types ...int) *WebsocketMessage {
- opChain := wm.chain.enter("NotType()")
- defer opChain.leave()
- wm.checkNotType(opChain, types...)
- return wm
- }
- func (wm *WebsocketMessage) checkType(opChain *chain, types ...int) {
- if opChain.failed() {
- return
- }
- if len(types) == 0 {
- opChain.fail(AssertionFailure{
- Type: AssertUsage,
- Errors: []error{
- errors.New("missing type argument"),
- },
- })
- return
- }
- found := false
- for _, t := range types {
- if t == wm.typ {
- found = true
- break
- }
- }
- if !found {
- if len(types) == 1 {
- opChain.fail(AssertionFailure{
- Type: AssertEqual,
- Actual: &AssertionValue{wsMessageType(wm.typ)},
- Expected: &AssertionValue{wsMessageType(types[0])},
- Errors: []error{
- errors.New("expected: message types are equal"),
- },
- })
- } else {
- typeList := make([]interface{}, 0, len(types))
- for _, t := range types {
- typeList = append(typeList, wsMessageType(t))
- }
- opChain.fail(AssertionFailure{
- Type: AssertBelongs,
- Actual: &AssertionValue{wsMessageType(wm.typ)},
- Expected: &AssertionValue{AssertionList(typeList)},
- Errors: []error{
- errors.New("expected: message type belongs to given list"),
- },
- })
- }
- }
- }
- func (wm *WebsocketMessage) checkNotType(opChain *chain, types ...int) {
- if opChain.failed() {
- return
- }
- if len(types) == 0 {
- opChain.fail(AssertionFailure{
- Type: AssertUsage,
- Errors: []error{
- errors.New("missing type argument"),
- },
- })
- return
- }
- found := false
- for _, t := range types {
- if t == wm.typ {
- found = true
- break
- }
- }
- if found {
- if len(types) == 1 {
- opChain.fail(AssertionFailure{
- Type: AssertNotEqual,
- Actual: &AssertionValue{wsMessageType(wm.typ)},
- Expected: &AssertionValue{wsMessageType(types[0])},
- Errors: []error{
- errors.New("expected: message types are non-equal"),
- },
- })
- } else {
- typeList := make([]interface{}, 0, len(types))
- for _, t := range types {
- typeList = append(typeList, wsMessageType(t))
- }
- opChain.fail(AssertionFailure{
- Type: AssertNotBelongs,
- Actual: &AssertionValue{wsMessageType(wm.typ)},
- Expected: &AssertionValue{AssertionList(typeList)},
- Errors: []error{
- errors.New("expected: message type does not belong to given list"),
- },
- })
- }
- }
- }
- // Code succeeds if WebSocket close code is one of the given.
- //
- // Code fails if WebSocket message type is not "8 - Connection Close Frame".
- //
- // WebSocket close codes are defined in RFC 6455, section 11.7.
- // See also https://godoc.org/github.com/gorilla/websocket#pkg-constants
- //
- // Example:
- //
- // msg := conn.Expect().Closed()
- // msg.Code(websocket.CloseNormalClosure, websocket.CloseGoingAway)
- func (wm *WebsocketMessage) Code(codes ...int) *WebsocketMessage {
- opChain := wm.chain.enter("Code()")
- defer opChain.leave()
- wm.checkCode(opChain, codes...)
- return wm
- }
- // NotCode succeeds if WebSocket close code is none of the given.
- //
- // NotCode fails if WebSocket message type is not "8 - Connection Close Frame".
- //
- // WebSocket close codes are defined in RFC 6455, section 11.7.
- // See also https://godoc.org/github.com/gorilla/websocket#pkg-constants
- //
- // Example:
- //
- // msg := conn.Expect().Closed()
- // msg.NotCode(websocket.CloseAbnormalClosure, websocket.CloseNoStatusReceived)
- func (wm *WebsocketMessage) NotCode(codes ...int) *WebsocketMessage {
- opChain := wm.chain.enter("NotCode()")
- defer opChain.leave()
- wm.checkNotCode(opChain, codes...)
- return wm
- }
- func (wm *WebsocketMessage) checkCode(opChain *chain, codes ...int) {
- if opChain.failed() {
- return
- }
- if len(codes) == 0 {
- opChain.fail(AssertionFailure{
- Type: AssertUsage,
- Errors: []error{
- errors.New("missing code argument"),
- },
- })
- return
- }
- if wm.typ != websocket.CloseMessage {
- opChain.fail(AssertionFailure{
- Type: AssertEqual,
- Actual: &AssertionValue{wsMessageType(wm.typ)},
- Expected: &AssertionValue{wsMessageType(websocket.CloseMessage)},
- Errors: []error{
- errors.New("expected: close message"),
- },
- })
- return
- }
- found := false
- for _, c := range codes {
- if c == wm.closeCode {
- found = true
- break
- }
- }
- if !found {
- if len(codes) == 1 {
- opChain.fail(AssertionFailure{
- Type: AssertEqual,
- Actual: &AssertionValue{wsCloseCode(wm.closeCode)},
- Expected: &AssertionValue{wsCloseCode(codes[0])},
- Errors: []error{
- errors.New("expected: close codes are equal"),
- },
- })
- } else {
- codeList := make([]interface{}, 0, len(codes))
- for _, c := range codes {
- codeList = append(codeList, wsCloseCode(c))
- }
- opChain.fail(AssertionFailure{
- Type: AssertBelongs,
- Actual: &AssertionValue{wsCloseCode(wm.closeCode)},
- Expected: &AssertionValue{AssertionList(codeList)},
- Errors: []error{
- errors.New("expected: close code belongs to given list"),
- },
- })
- }
- }
- }
- func (wm *WebsocketMessage) checkNotCode(opChain *chain, codes ...int) {
- if opChain.failed() {
- return
- }
- if len(codes) == 0 {
- opChain.fail(AssertionFailure{
- Type: AssertUsage,
- Errors: []error{
- errors.New("missing code argument"),
- },
- })
- return
- }
- if wm.typ != websocket.CloseMessage {
- opChain.fail(AssertionFailure{
- Type: AssertEqual,
- Actual: &AssertionValue{wsMessageType(wm.typ)},
- Expected: &AssertionValue{wsMessageType(websocket.CloseMessage)},
- Errors: []error{
- errors.New("expected: close message"),
- },
- })
- return
- }
- found := false
- for _, c := range codes {
- if c == wm.closeCode {
- found = true
- break
- }
- }
- if found {
- if len(codes) == 1 {
- opChain.fail(AssertionFailure{
- Type: AssertNotEqual,
- Actual: &AssertionValue{wsCloseCode(wm.closeCode)},
- Expected: &AssertionValue{wsCloseCode(codes[0])},
- Errors: []error{
- errors.New("expected: close codes are non-equal"),
- },
- })
- } else {
- codeList := make([]interface{}, 0, len(codes))
- for _, c := range codes {
- codeList = append(codeList, wsCloseCode(c))
- }
- opChain.fail(AssertionFailure{
- Type: AssertNotBelongs,
- Actual: &AssertionValue{wsCloseCode(wm.closeCode)},
- Expected: &AssertionValue{AssertionList(codeList)},
- Errors: []error{
- errors.New("expected: close code dose not belong to given list"),
- },
- })
- }
- }
- }
- // NoContent succeeds if WebSocket message has no content (is empty).
- func (wm *WebsocketMessage) NoContent() *WebsocketMessage {
- opChain := wm.chain.enter("NoContent()")
- defer opChain.leave()
- if opChain.failed() {
- return wm
- }
- if !(len(wm.content) == 0) {
- var actual interface{}
- switch wm.typ {
- case websocket.BinaryMessage:
- actual = wm.content
- default:
- actual = string(wm.content)
- }
- opChain.fail(AssertionFailure{
- Type: AssertEmpty,
- Actual: &AssertionValue{actual},
- Errors: []error{
- errors.New("expected: message content is empty"),
- },
- })
- }
- return wm
- }
- // Body returns a new String instance with WebSocket message content.
- //
- // Example:
- //
- // msg := conn.Expect()
- // msg.Body().NotEmpty()
- // msg.Body().Length().IsEqual(100)
- func (wm *WebsocketMessage) Body() *String {
- opChain := wm.chain.enter("Body()")
- defer opChain.leave()
- if opChain.failed() {
- return newString(opChain, "")
- }
- return newString(opChain, string(wm.content))
- }
- // JSON returns a new Value instance with JSON contents of WebSocket message.
- //
- // JSON succeeds if JSON may be decoded from message content.
- //
- // Example:
- //
- // msg := conn.Expect()
- // msg.JSON().Array().ConsistsOf("foo", "bar")
- func (wm *WebsocketMessage) JSON() *Value {
- opChain := wm.chain.enter("JSON()")
- defer opChain.leave()
- if opChain.failed() {
- return newValue(opChain, nil)
- }
- var value interface{}
- if err := json.Unmarshal(wm.content, &value); err != nil {
- opChain.fail(AssertionFailure{
- Type: AssertValid,
- Actual: &AssertionValue{
- string(wm.content),
- },
- Errors: []error{
- errors.New("failed to decode json"),
- err,
- },
- })
- return newValue(opChain, nil)
- }
- return newValue(opChain, value)
- }
- type wsMessageType int
- func (wmt wsMessageType) String() string {
- s := "unknown"
- switch wmt {
- case websocket.TextMessage:
- s = "text"
- case websocket.BinaryMessage:
- s = "binary"
- case websocket.CloseMessage:
- s = "close"
- case websocket.PingMessage:
- s = "ping"
- case websocket.PongMessage:
- s = "pong"
- }
- return fmt.Sprintf("%s(%d)", s, wmt)
- }
- type wsCloseCode int
- // https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent/code
- func (wcc wsCloseCode) String() string {
- s := "Unknown"
- switch wcc {
- case 1000:
- s = "NormalClosure"
- case 1001:
- s = "GoingAway"
- case 1002:
- s = "ProtocolError"
- case 1003:
- s = "UnsupportedData"
- case 1004:
- s = "Reserved"
- case 1005:
- s = "NoStatusReceived"
- case 1006:
- s = "AbnormalClosure"
- case 1007:
- s = "InvalidFramePayloadData"
- case 1008:
- s = "PolicyViolation"
- case 1009:
- s = "MessageTooBig"
- case 1010:
- s = "MandatoryExtension"
- case 1011:
- s = "InternalServerError"
- case 1012:
- s = "ServiceRestart"
- case 1013:
- s = "TryAgainLater"
- case 1014:
- s = "BadGateway"
- case 1015:
- s = "TLSHandshake"
- }
- return fmt.Sprintf("%s(%d)", s, wcc)
- }
|