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
}