2014-06-06 20:21:13 +00:00
|
|
|
package soap
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
2023-05-10 14:52:26 +00:00
|
|
|
"errors"
|
2014-06-06 20:21:13 +00:00
|
|
|
"io/ioutil"
|
|
|
|
"net/http"
|
|
|
|
"net/url"
|
|
|
|
"reflect"
|
2023-05-10 11:30:40 +00:00
|
|
|
"strings"
|
2014-06-06 20:21:13 +00:00
|
|
|
"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) {
|
2017-11-07 23:19:10 +00:00
|
|
|
t.Parallel()
|
2014-06-06 20:21:13 +00:00
|
|
|
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(`
|
|
|
|
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
|
|
|
|
<s:Body>
|
|
|
|
<u:myactionResponse xmlns:u="mynamespace">
|
|
|
|
<A>valueA</A>
|
|
|
|
<B>valueB</B>
|
|
|
|
</u:myactionResponse>
|
|
|
|
</s:Body>
|
|
|
|
</s:Envelope>
|
|
|
|
`)),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
client := SOAPClient{
|
|
|
|
EndpointURL: *url,
|
|
|
|
HTTPClient: http.Client{
|
|
|
|
Transport: rt,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
type In struct {
|
|
|
|
Foo string
|
|
|
|
Bar string `soap:"bar"`
|
2017-11-07 23:19:10 +00:00
|
|
|
Baz string
|
2014-06-06 20:21:13 +00:00
|
|
|
}
|
|
|
|
type Out struct {
|
|
|
|
A string
|
|
|
|
B string
|
|
|
|
}
|
2017-11-07 23:19:10 +00:00
|
|
|
in := In{"foo", "bar", "quoted=\"baz\""}
|
2014-06-06 20:21:13 +00:00
|
|
|
gotOut := Out{}
|
|
|
|
err = client.PerformAction("mynamespace", "myaction", &in, &gotOut)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
wantBody := (soapPrefix +
|
|
|
|
`<u:myaction xmlns:u="mynamespace">` +
|
|
|
|
`<Foo>foo</Foo>` +
|
|
|
|
`<bar>bar</bar>` +
|
2017-11-07 23:19:10 +00:00
|
|
|
`<Baz>quoted="baz"</Baz>` +
|
2014-06-06 20:21:13 +00:00
|
|
|
`</u:myaction>` +
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
2017-11-07 23:19:10 +00:00
|
|
|
|
2023-05-10 11:30:40 +00:00
|
|
|
func TestUPnPError(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
url, err := url.Parse("http://example.com/soap")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
body := `
|
|
|
|
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
|
|
|
|
<s:Body>
|
|
|
|
<s:Fault>
|
|
|
|
<faultcode>s:Client</faultcode>
|
|
|
|
<faultstring>UPnPError</faultstring>
|
|
|
|
<detail>
|
|
|
|
<UPnPError xmlns="urn:schemas-upnp-org:control-1-0">
|
|
|
|
<errorCode>725</errorCode>
|
|
|
|
<errorDescription>OnlyPermanentLeasesSupported</errorDescription>
|
|
|
|
</UPnPError>
|
|
|
|
</detail>
|
|
|
|
</s:Fault>
|
|
|
|
</s:Body>
|
|
|
|
</s:Envelope>`
|
|
|
|
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)
|
|
|
|
}
|
2023-05-10 14:52:26 +00:00
|
|
|
soapErr := &SOAPFaultError{}
|
|
|
|
if ok := errors.As(err, &soapErr); !ok {
|
2023-05-10 11:30:40 +00:00
|
|
|
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)
|
|
|
|
}
|
2023-05-10 12:05:19 +00:00
|
|
|
|
2023-05-10 11:30:40 +00:00
|
|
|
if !strings.EqualFold(string(soapErr.Detail.Raw), `
|
2023-05-10 12:05:19 +00:00
|
|
|
<UPnPError xmlns="urn:schemas-upnp-org:control-1-0">
|
|
|
|
<errorCode>725</errorCode>
|
|
|
|
<errorDescription>OnlyPermanentLeasesSupported</errorDescription>
|
|
|
|
</UPnPError>
|
|
|
|
`) {
|
2023-05-10 11:30:40 +00:00
|
|
|
t.Fatalf("unexpected Detail.Raw, got:\n%s", string(soapErr.Detail.Raw))
|
|
|
|
}
|
|
|
|
}
|
2017-11-07 23:19:10 +00:00
|
|
|
|
|
|
|
func TestEscapeXMLText(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
|
|
input string
|
|
|
|
want string
|
|
|
|
}{
|
|
|
|
{"", ""},
|
|
|
|
{"abc123", "abc123"},
|
|
|
|
{"<foo>&", "<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)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|