mirror of
https://github.com/aclindsa/ofxgo.git
synced 2024-11-21 19:20:05 -05:00
Aaron Lindsay
35c7116654
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.
174 lines
6.1 KiB
Go
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)
|
|
}
|