Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 112 additions & 22 deletions transaction/transaction.go
Original file line number Diff line number Diff line change
@@ -1,36 +1,126 @@
package transaction

import "encoding/json"
import (
"bytes"
"encoding/json"
"fmt"
"reflect"
)

// Transaction represents a Dredd transaction object.
// http://dredd.readthedocs.org/en/latest/hooks/#transaction-object-structure
type Transaction struct {
Name string `json:"name,omitempty"`
Host string `json:"host,omitempty"`
Port string `json:"port,omitempty"`
Protocol string `json:"protocol,omitempty"`
FullPath string `json:"fullPath,omitempty"`
Request *struct {
Body string `json:"body,omitempty"`
Headers map[string]interface{} `json:"headers,omitempty"`
URI string `json:"uri,omitempty"`
Method string `json:"method,omitempty"`
} `json:"request,omitempty"`
Name string `json:"name,omitempty"`
Host string `json:"host,omitempty"`
Port string `json:"port,omitempty"`
Protocol string `json:"protocol,omitempty"`
FullPath string `json:"fullPath,omitempty"`
Request *Request `json:"request,omitempty"`
Expected *json.RawMessage `json:"expected,omitempty"`
Real *struct {
Body string `json:"body"`
Headers map[string]interface{} `json:"headers"`
StatusCode int `json:"statusCode"`
} `json:"real,omitempty"`
Origin *json.RawMessage `json:"origin,omitempty"`
Test *json.RawMessage `json:"test,omitempty"`
Results *json.RawMessage `json:"results,omitempty"`
Skip bool `json:"skip,omitempty"`
Fail interface{} `json:"fail,omitempty"`
Real *RealRequest `json:"real,omitempty"`
Origin *json.RawMessage `json:"origin,omitempty"`
Test *json.RawMessage `json:"test,omitempty"`
Results *json.RawMessage `json:"results,omitempty"`
Skip bool `json:"skip,omitempty"`
Fail interface{} `json:"fail,omitempty"`

TestOrder []string `json:"hooks_modifications,omitempty"`
}

type Request struct {
Body string `json:"body,omitempty"`
Headers Headers `json:"headers,omitempty"`
URI string `json:"uri,omitempty"`
Method string `json:"method,omitempty"`
}

type RealRequest struct {
Body string `json:"body"`
Headers Headers `json:"headers"`
StatusCode int `json:"statusCode"`
}

type Headers map[string][]string

func (hdrs *Headers) MarshalJSON() ([]byte, error) {
buf := bytes.Buffer{}
buf.WriteByte('{')

first := true
for k, v := range *hdrs {
if first {
first = false
} else {
buf.WriteString(",")
}
numValues := len(v)
writeJSONKeyToBuffer(&buf, string(k))
if numValues == 1 {
writeJSONValueToBuffer(&buf, v[0])
continue
}
data, err := json.Marshal(v)

if err != nil {
return nil, err
}
buf.WriteString(string(data))
}
buf.WriteByte('}')
return buf.Bytes(), nil
}

func writeJSONKeyToBuffer(buf *bytes.Buffer, key string) {
writeJSONValueToBuffer(buf, key)
buf.WriteByte(':')
}

func writeJSONValueToBuffer(buf *bytes.Buffer, key string) {
buf.WriteString(`"` + key + `"`)
}

func (hdrs *Headers) UnmarshalJSON(data []byte) error {
var headers map[string]interface{} = make(map[string]interface{})
err := json.Unmarshal(data, &headers)

if err != nil {
return err
}

*hdrs = make(map[string][]string)
for k := range headers {
header := headers[k]
value := reflect.ValueOf(header)
kind := value.Kind()

switch kind {
case reflect.Float32, reflect.Float64, reflect.Int, reflect.String:
(*hdrs)[k] = []string{
stringFromValue(value),
}
case reflect.Slice:
for i := 0; i < value.Len(); i++ {
(*hdrs)[k] = append((*hdrs)[k], stringFromValueAtIndex(value, i))
}

default:
// TODO: Figure out how to use this
panic(fmt.Sprintf("unsupported Kind %s", kind.String()))
}

}

return nil
}

func stringFromValueAtIndex(val reflect.Value, index int) string {
return fmt.Sprint(val.Index(index).Interface())
}

func stringFromValue(val reflect.Value) string {
return fmt.Sprint(val.Interface())
}

// AddTestOrderPoint adds a value to the hooks_modification key used when
// running dredd with TEST_DREDD_HOOKS_HANDLER_ORDER enabled.
func (t *Transaction) AddTestOrderPoint(value string) {
Expand Down
73 changes: 72 additions & 1 deletion transaction/transaction_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
package transaction

import "testing"
import (
"encoding/json"
"reflect"
"testing"
)

type HeadersWrapper struct {
Headers Headers `json:"headers,omitempty"`
}

func TestSettingFailToString(t *testing.T) {
trans := Transaction{}
Expand All @@ -10,3 +18,66 @@ func TestSettingFailToString(t *testing.T) {
t.Errorf("Transaction.Fail member must be able to be set as string")
}
}

func TestHeadersJsonUnmarshal(t *testing.T) {
data := []byte(`{
"headers":{
"User-Agent":"Dredd/1.4.0 (Darwin 15.4.0; x64)",
"Content-Length":11,
"Set-Cookie":["Test=Yo"]
}
}`)

hdrs := &HeadersWrapper{}
err := json.Unmarshal(data, hdrs)
if err != nil {
t.Errorf("Error %v", err)
}

if !reflect.DeepEqual(hdrs.Headers["Set-Cookie"], []string{"Test=Yo"}) {
t.Errorf("Set-Cookie should be slice with length of 1")
}

if !reflect.DeepEqual(hdrs.Headers["User-Agent"], []string{"Dredd/1.4.0 (Darwin 15.4.0; x64)"}) {
t.Errorf("User-Agent should be converted to a slice")
}

if !reflect.DeepEqual(hdrs.Headers["Content-Length"], []string{"11"}) {
t.Errorf("Content-Length should be converted to a string and be a slice")
}
}

func TestHeadersMarshalJSON(t *testing.T) {
headers := make(map[string][]string)
headers["Content-Type"] = []string{"9"}
headers["User-Agent"] = []string{"Dredd/1.4.0 (Darwin 15.4.0; x64)"}
headers["Set-Cookie"] = []string{"Test=Yo", "Another=This"}
wrapper := &HeadersWrapper{
Headers: headers,
}

data, err := json.Marshal(wrapper)

if err != nil {
t.Errorf("json.Marshal failed with %v", err)
}

var expected interface{}
err = json.Unmarshal([]byte(`{"headers":{"Content-Type":"9","User-Agent":"Dredd/1.4.0 (Darwin 15.4.0; x64)","Set-Cookie":["Test=Yo","Another=This"]}}`), &expected)

if err != nil {
t.Errorf("failed to unmarshal expected json string")
}

var actual interface{}
err = json.Unmarshal(data, &actual)

if err != nil {
t.Errorf("failed to unmarshal Marshaled json")
}

if !reflect.DeepEqual(expected, actual) {
t.Errorf("json.Marshal failed, data \n %#v did not matched expected \n %#v", actual, expected)

}
}