1
0
mirror of https://github.com/aclindsa/ofxgo.git synced 2024-10-30 05:20:05 -04:00
ofxgo/response_test.go
Aaron Lindsay 35c7116654 Add the ability to marshal a Response to SGML/XML and test it
This allows for ofxgo to be used to create well-formatted OFX from poor
OFX, or even be used to generate OFX from other formats for easier
importing into financial management software.

Test this functionality by adding "round trip" testing to all existing
tests - ensure that responses' content is the same after a round trip of
marshalling and unmarshalling them.
2019-03-02 07:03:11 -05:00

174 lines
6.1 KiB
Go

package ofxgo_test
import (
"fmt"
"github.com/aclindsa/ofxgo"
"github.com/aclindsa/xml"
"os"
"path/filepath"
"reflect"
"testing"
)
// Attempt to find a method on the provided Value called 'Equal' which is a
// receiver for the Value, takes one argument of the same type, and returns
// one bool. equalMethodOf() returns the nil value if the method couldn't be
// found.
func equalMethodOf(v reflect.Value) reflect.Value {
if equalMethod, ok := v.Type().MethodByName("Equal"); ok {
if !equalMethod.Func.IsNil() &&
equalMethod.Type.NumIn() == 2 &&
equalMethod.Type.In(0) == v.Type() &&
equalMethod.Type.In(1) == v.Type() &&
equalMethod.Type.NumOut() == 1 &&
equalMethod.Type.Out(0).Kind() == reflect.Bool {
return v.MethodByName("Equal")
}
}
return reflect.ValueOf(nil)
}
// Attempt to return a string representation of the value appropriate for its
// type by finding a method on the provided Value called 'String' which is a
// receiver for the Value, and returns one string. stringMethodOf() returns
// fmt.Sprintf("%s", v) if it can't find a String method.
func valueToString(v reflect.Value) string {
if equalMethod, ok := v.Type().MethodByName("String"); ok {
if !equalMethod.Func.IsNil() &&
equalMethod.Type.NumIn() == 1 &&
equalMethod.Type.In(0) == v.Type() &&
equalMethod.Type.NumOut() == 1 &&
equalMethod.Type.Out(0).Kind() == reflect.String {
out := v.MethodByName("String").Call([]reflect.Value{})
return out[0].String()
}
}
return fmt.Sprintf("%s", v)
}
// Recursively check that the expected and actual Values are equal in value.
// If the two Values are equal in type and contain an appropriate Equal()
// method (see equalMethodOf()), that method is used for comparison. The
// provided testing.T is failed with a message if any inequality is found.
func checkEqual(t *testing.T, fieldName string, expected, actual reflect.Value) {
if expected.IsValid() && !actual.IsValid() {
t.Fatalf("%s: %s was unexpectedly nil\n", t.Name(), fieldName)
} else if !expected.IsValid() && actual.IsValid() {
t.Fatalf("%s: Expected %s to be nil (it wasn't)\n", t.Name(), fieldName)
} else if !expected.IsValid() && !actual.IsValid() {
return
}
if expected.Type() != actual.Type() {
t.Fatalf("%s: Expected %s type for %s, found %s\n", t.Name(), expected.Type(), fieldName, actual.Type())
}
equalMethod := equalMethodOf(expected)
if equalMethod.IsValid() {
in := []reflect.Value{actual}
out := equalMethod.Call(in)
if !out[0].Bool() {
t.Fatalf("%s: %s !Equal(): expected '%s', got '%s'\n", t.Name(), fieldName, valueToString(expected), valueToString(actual))
}
return
}
switch expected.Kind() {
case reflect.Array:
for i := 0; i < expected.Len(); i++ {
checkEqual(t, fmt.Sprintf("%s[%d]", fieldName, i), expected.Index(i), actual.Index(i))
}
case reflect.Slice:
if !expected.IsNil() && actual.IsNil() {
t.Fatalf("%s: %s was unexpectedly nil\n", t.Name(), fieldName)
} else if expected.IsNil() && !actual.IsNil() {
t.Fatalf("%s: Expected %s to be nil (it wasn't)\n", t.Name(), fieldName)
}
if expected.Len() != actual.Len() {
t.Fatalf("%s: Expected len(%s) to to be %d, was %d\n", t.Name(), fieldName, expected.Len(), actual.Len())
}
for i := 0; i < expected.Len(); i++ {
checkEqual(t, fmt.Sprintf("%s[%d]", fieldName, i), expected.Index(i), actual.Index(i))
}
case reflect.Interface:
if !expected.IsNil() && actual.IsNil() {
t.Fatalf("%s: %s was unexpectedly nil\n", t.Name(), fieldName)
} else if expected.IsNil() && !actual.IsNil() {
t.Fatalf("%s: Expected %s to be nil (it wasn't)\n", t.Name(), fieldName)
}
checkEqual(t, fieldName, expected.Elem(), actual.Elem())
case reflect.Ptr:
checkEqual(t, fieldName, expected.Elem(), actual.Elem())
case reflect.Struct:
structType := expected.Type()
for i, n := 0, expected.NumField(); i < n; i++ {
field := structType.Field(i)
// skip XMLName fields so we can be lazy and not fill them out in
// testing code
var xmlname xml.Name
if field.Name == "XMLName" && field.Type == reflect.TypeOf(xmlname) {
continue
}
// Construct a new field name for this field, containing the parent
// fieldName
newFieldName := fieldName
if fieldName != "" {
newFieldName = fieldName + "."
}
newFieldName = newFieldName + field.Name
checkEqual(t, newFieldName, expected.Field(i), actual.Field(i))
}
case reflect.String:
if expected.String() != actual.String() {
t.Fatalf("%s: %s expected to be '%s', found '%s'\n", t.Name(), fieldName, expected.String(), actual.String())
}
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
if expected.Uint() != actual.Uint() {
t.Fatalf("%s: %s expected to be '%s', found '%s'\n", t.Name(), fieldName, valueToString(expected), valueToString(actual))
}
default:
t.Fatalf("%s: %s has unexpected type that didn't provide an Equal() method: %s\n", t.Name(), fieldName, expected.Type().Name())
}
}
func checkResponsesEqual(t *testing.T, expected, actual *ofxgo.Response) {
checkEqual(t, "", reflect.ValueOf(expected), reflect.ValueOf(actual))
}
func checkResponseRoundTrip(t *testing.T, response *ofxgo.Response) {
b, err := response.Marshal()
if err != nil {
t.Fatalf("Unexpected error re-marshaling OFX response: %s\n", err)
}
roundtripped, err := ofxgo.ParseResponse(b)
if err != nil {
t.Fatalf("Unexpected error re-parsing OFX response: %s\n", err)
}
checkResponsesEqual(t, response, roundtripped)
}
// Ensure that these samples both parse without errors, and can be converted
// back and forth without changing.
func TestValidSamples(t *testing.T) {
fn := func(path string, info os.FileInfo, err error) error {
if info.IsDir() {
return nil
} else if ext := filepath.Ext(path); ext != ".ofx" && ext != ".qfx" {
return nil
}
file, err := os.Open(path)
if err != nil {
t.Fatalf("Unexpected error opening %s: %s\n", path, err)
}
response, err := ofxgo.ParseResponse(file)
if err != nil {
t.Fatalf("Unexpected error parsing OFX response in %s: %s\n", path, err)
}
checkResponseRoundTrip(t, response)
return nil
}
filepath.Walk("samples/valid_responses", fn)
filepath.Walk("samples/busted_responses", fn)
}