package xmlrpc import ( "fmt" "io" "io/ioutil" "reflect" "testing" "time" "golang.org/x/text/encoding/charmap" "golang.org/x/text/transform" ) type book struct { Title string Amount int } type bookUnexported struct { title string amount int } type timestamp time.Time var unmarshalTests = []struct { value interface{} ptr interface{} xml string }{ // int, i4, i8 {0, new(*int), ""}, {100, new(*int), "100"}, {389451, new(*int), "389451"}, {int64(45659074), new(*int64), "45659074"}, // string {"Once upon a time", new(*string), "Once upon a time"}, {"Mike & Mick ", new(*string), "Mike & Mick <London, UK>"}, {"Once upon a time", new(*string), "Once upon a time"}, // base64 {"T25jZSB1cG9uIGEgdGltZQ==", new(*string), "T25jZSB1cG9uIGEgdGltZQ=="}, // boolean {true, new(*bool), "1"}, {false, new(*bool), "0"}, // double {12.134, new(*float32), "12.134"}, {-12.134, new(*float32), "-12.134"}, {time.Unix(1386622812, 0).UTC(), new(*time.Time), "20131209T21:00:12"}, {time.Unix(1386622812, 0).UTC(), new(*time.Time), "2013-12-09T21:00:12Z"}, {time.Unix(1386622812, 0).UTC(), new(*timestamp), "20131209T21:00:12"}, {time.Unix(1386622812, 0).UTC(), new(*timestamp), "2013-12-09T21:00:12Z"}, // datetime.iso8601 {_time("2013-12-09T21:00:12Z"), new(*time.Time), "20131209T21:00:12"}, {_time("2013-12-09T21:00:12Z"), new(*time.Time), "20131209T21:00:12Z"}, {_time("2013-12-09T21:00:12-01:00"), new(*time.Time), "20131209T21:00:12-01:00"}, {_time("2013-12-09T21:00:12+01:00"), new(*time.Time), "20131209T21:00:12+01:00"}, {_time("2013-12-09T21:00:12Z"), new(*time.Time), "2013-12-09T21:00:12"}, {_time("2013-12-09T21:00:12Z"), new(*time.Time), "2013-12-09T21:00:12Z"}, {_time("2013-12-09T21:00:12-01:00"), new(*time.Time), "2013-12-09T21:00:12-01:00"}, {_time("2013-12-09T21:00:12+01:00"), new(*time.Time), "2013-12-09T21:00:12+01:00"}, // array {[]int{1, 5, 7}, new(*[]int), "157"}, {[]interface{}{"A", "5"}, new(interface{}), "A5"}, {[]interface{}{"A", int64(5)}, new(interface{}), "A5"}, // struct {book{"War and Piece", 20}, new(*book), "TitleWar and PieceAmount20"}, {bookUnexported{}, new(*bookUnexported), "titleWar and Pieceamount20"}, {map[string]interface{}{"Name": "John Smith"}, new(interface{}), "NameJohn Smith"}, {map[string]interface{}{}, new(interface{}), ""}, } func _time(s string) time.Time { t, err := time.Parse(time.RFC3339, s) if err != nil { panic(fmt.Sprintf("time parsing error: %v", err)) } return t } func Test_unmarshal(t *testing.T) { for _, tt := range unmarshalTests { v := reflect.New(reflect.TypeOf(tt.value)) if err := unmarshal([]byte(wrap_xml(tt.xml)), v.Interface()); err != nil { t.Fatalf("unmarshal error: %v.\n\tFailed on %v\n", err, tt.value) } v = v.Elem() if v.Kind() == reflect.Slice { vv := reflect.ValueOf(tt.value) if vv.Len() != v.Len() { t.Fatalf("unmarshal error:\nexpected: %v\n got: %v", tt.value, v.Interface()) } for i := 0; i < v.Len(); i++ { if v.Index(i).Interface() != vv.Index(i).Interface() { t.Fatalf("unmarshal error:\nexpected: %v\n got: %v", tt.value, v.Interface()) } } } else { a1 := v.Interface() a2 := interface{}(tt.value) if !reflect.DeepEqual(a1, a2) { t.Fatalf("unmarshal error:\nexpected: %v\n got: %v", tt.value, v.Interface()) } } } } func Test_unmarshalToNil(t *testing.T) { for _, tt := range unmarshalTests { if err := unmarshal([]byte(tt.xml), tt.ptr); err != nil { t.Fatalf("unmarshal error: %v\n\tFailed on %v", err, tt.xml) } } } func Test_typeMismatchError(t *testing.T) { var s string encoded := "100" var err error if err = unmarshal([]byte(encoded), &s); err == nil { t.Fatal("unmarshal error: expected error, but didn't get it") } if _, ok := err.(TypeMismatchError); !ok { t.Fatal("unmarshal error: expected type mistmatch error, but didn't get it") } } func Test_unmarshalEmptyValueTag(t *testing.T) { var v int if err := unmarshal([]byte(""), &v); err != nil { t.Fatalf("unmarshal error: %v", err) } } const structEmptyXML = ` ` func Test_unmarshalEmptyStruct(t *testing.T) { var v interface{} if err := unmarshal([]byte(structEmptyXML), &v); err != nil { t.Fatal(err) } if v == nil { t.Fatalf("got nil map") } } const arrayValueXML = ` 234 1 Hello World Extra Value ` func Test_unmarshalExistingArray(t *testing.T) { var ( v1 int v2 bool v3 string v = []interface{}{&v1, &v2, &v3} ) if err := unmarshal([]byte(wrap_xml(arrayValueXML)), &v); err != nil { t.Fatal(err) } // check pre-existing values if want := 234; v1 != want { t.Fatalf("want %d, got %d", want, v1) } if want := true; v2 != want { t.Fatalf("want %t, got %t", want, v2) } if want := "Hello World"; v3 != want { t.Fatalf("want %s, got %s", want, v3) } // check the appended result if n := len(v); n != 4 { t.Fatalf("missing appended result") } if got, ok := v[3].(string); !ok || got != "Extra Value" { t.Fatalf("got %s, want %s", got, "Extra Value") } } func Test_decodeNonUTF8Response(t *testing.T) { data, err := ioutil.ReadFile("fixtures/cp1251.xml") if err != nil { t.Fatal(err) } CharsetReader = decode var s string if err = unmarshal(data, &s); err != nil { fmt.Println(err) t.Fatal("unmarshal error: cannot decode non utf-8 response") } expected := "Л.Н. Толстой - Война и Мир" if s != expected { t.Fatalf("unmarshal error:\nexpected: %v\n got: %v", expected, s) } CharsetReader = nil } type server struct { Hostname string Speed int Data []int } var unmarshalTestsSL = []struct { value interface{} ptr interface{} xml string }{ {100, new(*int), "100"}, {[]int{31}, new(*[]int), "31"}, {book{"War and Piece", 20}, new(*book), "TitleWar and PieceAmount20"}, {server{"Testing", 10, []int{1,2}}, new(*server), "Hostname" + "TestingSpeed10" + "Data12" + ""}, {[]server{{"Toasting", 11, []int{2,3}}}, new(*[]server), "Hostname" + "ToastingSpeed11" + "Data23" + ""}, } func Test_unmarshalMismatchCorrection(t *testing.T) { // This test is for SoftLayer specific responses. // If a single value is returned, a struct is the response instead of a splice. // So we need to coerce the result back to a splice. for _, tt := range unmarshalTestsSL { v := reflect.New(reflect.TypeOf(tt.value)) if err := unmarshal([]byte(wrap_xml(tt.xml)), v.Interface()); err != nil { t.Fatalf("unmarshal error: %v.\n\tFailed on %v\n", err, tt.value) } v = v.Elem() if v.Kind() == reflect.Slice { vv := reflect.ValueOf(tt.value) if vv.Len() != v.Len() { t.Fatalf("unmarshal error:\nexpected: %v\n got: %v", tt.value, v.Interface()) } for i := 0; i < v.Len(); i++ { a1 := v.Index(i).Interface() a2 := vv.Index(i).Interface() // Checks for a slice element that contains structs if v.Index(i).Kind() == reflect.Struct { if !reflect.DeepEqual(a1, a2) { t.Fatalf("unmarshal error:\nexpected: %v\n got: %v", a1, a2) } } else { if a1 != a2 { t.Fatalf("unmarshal error:\nexpected: %v\n got: %v", a1, a2) } } } } else { a1 := v.Interface() a2 := interface{}(tt.value) if !reflect.DeepEqual(a1, a2) { t.Fatalf("unmarshal error:\nexpected: %v\n got: %v", tt.value, v.Interface()) } } } } func wrap_xml(xml_string string) string { head := "" tail := "" return string(head + xml_string + tail) } func decode(charset string, input io.Reader) (io.Reader, error) { if charset != "cp1251" { return nil, fmt.Errorf("unsupported charset") } return transform.NewReader(input, charmap.Windows1251.NewDecoder()), nil }