package soap import ( "bytes" "errors" "io/ioutil" "net/http" "net/url" "reflect" "strings" "testing" ) type capturingRoundTripper struct { err error resp *http.Response capturedReq *http.Request } func (rt *capturingRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { rt.capturedReq = req return rt.resp, rt.err } func TestActionInputs(t *testing.T) { t.Parallel() url, err := url.Parse("http://example.com/soap") if err != nil { t.Fatal(err) } rt := &capturingRoundTripper{ err: nil, resp: &http.Response{ StatusCode: 200, Body: ioutil.NopCloser(bytes.NewBufferString(` valueA valueB `)), }, } client := SOAPClient{ EndpointURL: *url, HTTPClient: http.Client{ Transport: rt, }, } type In struct { Foo string Bar string `soap:"bar"` Baz string } type Out struct { A string B string } in := In{"foo", "bar", "quoted=\"baz\""} gotOut := Out{} err = client.PerformAction("mynamespace", "myaction", &in, &gotOut) if err != nil { t.Fatal(err) } wantBody := (soapPrefix + `` + `foo` + `bar` + `quoted="baz"` + `` + soapSuffix) body, err := ioutil.ReadAll(rt.capturedReq.Body) if err != nil { t.Fatal(err) } gotBody := string(body) if wantBody != gotBody { t.Errorf("Bad request body\nwant: %q\n got: %q", wantBody, gotBody) } wantOut := Out{"valueA", "valueB"} if !reflect.DeepEqual(wantOut, gotOut) { t.Errorf("Bad output\nwant: %+v\n got: %+v", wantOut, gotOut) } } func TestUPnPError(t *testing.T) { t.Parallel() url, err := url.Parse("http://example.com/soap") if err != nil { t.Fatal(err) } body := ` s:Client UPnPError 725 OnlyPermanentLeasesSupported ` rt := &capturingRoundTripper{ resp: &http.Response{ StatusCode: 500, ContentLength: int64(len(body)), Body: ioutil.NopCloser(bytes.NewBufferString(body)), }, } client := SOAPClient{ EndpointURL: *url, HTTPClient: http.Client{ Transport: rt, }, } err = client.PerformAction("mynamespace", "myaction", nil, nil) if err == nil { t.Fatal("expected error, got nil") } if testing.Verbose() { t.Logf("%+v\n", err) } soapErr := &SOAPFaultError{} if ok := errors.As(err, &soapErr); !ok { t.Fatal("expected *SOAPFaultError") } if soapErr.FaultCode != "s:Client" { t.Fatalf("unexpected FaultCode: %s", soapErr.FaultCode) } if soapErr.FaultString != "UPnPError" { t.Fatalf("unexpected FaultString: %s", soapErr.FaultString) } if soapErr.Detail.UPnPError.Errorcode != 725 { t.Fatalf("unexpected UPnPError Errorcode: %d", soapErr.Detail.UPnPError.Errorcode) } if soapErr.Detail.UPnPError.ErrorDescription != "OnlyPermanentLeasesSupported" { t.Fatalf("unexpected UPnPError ErrorDescription: %s", soapErr.Detail.UPnPError.ErrorDescription) } if !strings.EqualFold(string(soapErr.Detail.Raw), ` 725 OnlyPermanentLeasesSupported `) { t.Fatalf("unexpected Detail.Raw, got:\n%s", string(soapErr.Detail.Raw)) } } func TestEscapeXMLText(t *testing.T) { t.Parallel() tests := []struct { input string want string }{ {"", ""}, {"abc123", "abc123"}, {"&", "<foo>&"}, {"\"foo'", "\"foo'"}, } for _, test := range tests { test := test t.Run(test.input, func(t *testing.T) { got := escapeXMLText(test.input) if got != test.want { t.Errorf("want %q, got %q", test.want, got) } }) } }