2026-07-08 15:17:07 +08:00
|
|
|
package internal
|
|
|
|
|
|
|
|
|
|
import (
|
2026-07-08 16:23:00 +08:00
|
|
|
"context"
|
2026-07-08 15:17:07 +08:00
|
|
|
"errors"
|
|
|
|
|
"fmt"
|
|
|
|
|
"io"
|
|
|
|
|
"net"
|
|
|
|
|
"net/http"
|
|
|
|
|
"net/http/httptest"
|
2026-07-08 16:23:00 +08:00
|
|
|
"strings"
|
2026-07-08 15:17:07 +08:00
|
|
|
"testing"
|
|
|
|
|
|
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func TestClientSend_SOAPFault(t *testing.T) {
|
|
|
|
|
ciims := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
w.Header().Set("Content-Type", "text/xml; charset=UTF-8")
|
|
|
|
|
fmt.Fprint(w, ErrMsg)
|
|
|
|
|
}))
|
|
|
|
|
defer ciims.Close()
|
|
|
|
|
|
|
|
|
|
client, err := NewClient(10)
|
|
|
|
|
require.NoError(t, err)
|
2026-07-08 16:23:00 +08:00
|
|
|
resp, err := client.Send(context.Background(), ciims.URL+"/services/ExchangeService", "<dummy>")
|
2026-07-08 15:17:07 +08:00
|
|
|
assert.NoError(t, err)
|
|
|
|
|
require.NotNil(t, resp)
|
|
|
|
|
|
|
|
|
|
assert.True(t, resp.IsFault())
|
|
|
|
|
assert.Equal(t, "Can not find the event [FLOP-ESTT-ATC-ALL1]", resp.ErrorMessage())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestClientSend_Success(t *testing.T) {
|
|
|
|
|
ciims := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
w.Header().Set("Content-Type", "text/xml; charset=UTF-8")
|
|
|
|
|
fmt.Fprint(w, SendOk)
|
|
|
|
|
}))
|
|
|
|
|
defer ciims.Close()
|
|
|
|
|
|
|
|
|
|
client, err := NewClient(10)
|
|
|
|
|
require.NoError(t, err)
|
2026-07-08 16:23:00 +08:00
|
|
|
resp, err := client.Send(context.Background(), ciims.URL+"/services/ExchangeService", "<dummy>")
|
2026-07-08 15:17:07 +08:00
|
|
|
assert.NoError(t, err)
|
|
|
|
|
require.NotNil(t, resp)
|
|
|
|
|
|
|
|
|
|
assert.Equal(t, "", resp.ErrorMessage())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestClientSend_ReceiveMessages(t *testing.T) {
|
|
|
|
|
ciims := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
w.Header().Set("Content-Type", "text/xml; charset=UTF-8")
|
|
|
|
|
fmt.Fprint(w, ReceiveResp)
|
|
|
|
|
}))
|
|
|
|
|
defer ciims.Close()
|
|
|
|
|
|
|
|
|
|
client, err := NewClient(10)
|
|
|
|
|
require.NoError(t, err)
|
2026-07-08 16:23:00 +08:00
|
|
|
resp, err := client.Send(context.Background(), ciims.URL+"/services/ExchangeService", "<dummy>")
|
2026-07-08 15:17:07 +08:00
|
|
|
assert.NoError(t, err)
|
|
|
|
|
require.NotNil(t, resp)
|
|
|
|
|
|
|
|
|
|
msgs := resp.Messages()
|
|
|
|
|
assert.Equal(t, 2, len(msgs))
|
|
|
|
|
assert.Equal(t, Msg, msgs[0])
|
|
|
|
|
assert.Equal(t, Msg, msgs[1])
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestClientSend_NetworkTimeout(t *testing.T) {
|
|
|
|
|
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
addr := listener.Addr().String()
|
|
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
|
conn, _ := listener.Accept()
|
|
|
|
|
if conn != nil {
|
|
|
|
|
io.Copy(io.Discard, conn)
|
|
|
|
|
conn.Close()
|
|
|
|
|
}
|
|
|
|
|
}()
|
|
|
|
|
|
|
|
|
|
client, err := NewClient(1)
|
|
|
|
|
require.NoError(t, err)
|
2026-07-08 16:23:00 +08:00
|
|
|
_, err = client.Send(context.Background(), "http://"+addr+"/services/ExchangeService", "<dummy>")
|
2026-07-08 15:17:07 +08:00
|
|
|
listener.Close()
|
|
|
|
|
assert.Error(t, err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestClientSend_ServerError(t *testing.T) {
|
|
|
|
|
client, err := NewClient(1)
|
|
|
|
|
require.NoError(t, err)
|
2026-07-08 16:23:00 +08:00
|
|
|
_, err = client.Send(context.Background(), "http://127.0.0.1:1/nonexistent", "<dummy>")
|
2026-07-08 15:17:07 +08:00
|
|
|
assert.Error(t, err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestClientSend_HTTPStatusError(t *testing.T) {
|
|
|
|
|
ciims := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
http.Error(w, "backend failed", http.StatusInternalServerError)
|
|
|
|
|
}))
|
|
|
|
|
defer ciims.Close()
|
|
|
|
|
|
|
|
|
|
client, err := NewClient(10)
|
|
|
|
|
require.NoError(t, err)
|
2026-07-08 16:23:00 +08:00
|
|
|
resp, err := client.Send(context.Background(), ciims.URL+"/services/ExchangeService", "<dummy>")
|
2026-07-08 15:17:07 +08:00
|
|
|
require.Error(t, err)
|
|
|
|
|
require.NotNil(t, resp)
|
|
|
|
|
|
|
|
|
|
var statusErr *HTTPStatusError
|
|
|
|
|
require.True(t, errors.As(err, &statusErr))
|
|
|
|
|
assert.Equal(t, http.StatusInternalServerError, statusErr.StatusCode)
|
|
|
|
|
assert.Contains(t, statusErr.Body, "backend failed")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestGetMsgs_Empty(t *testing.T) {
|
|
|
|
|
msgs := GetMsgs("<soap><body></body></soap>")
|
|
|
|
|
assert.Equal(t, 0, len(msgs))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestGetMsgs_DifferentNamespacePrefix(t *testing.T) {
|
|
|
|
|
resp := `<soap:Envelope><soap:Body><ns2:receiveResponse xmlns:ns2="http://ciims.bhia.itdcl.com/ExchangeService">
|
|
|
|
|
<ns2:out><ns2:string><MSG/></ns2:string></ns2:out>
|
|
|
|
|
</ns2:receiveResponse></soap:Body></soap:Envelope>`
|
|
|
|
|
msgs := GetMsgs(resp)
|
|
|
|
|
assert.Equal(t, 1, len(msgs))
|
|
|
|
|
assert.Equal(t, "<MSG/>", msgs[0])
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestGetMsgs_AttributesWhitespaceAndMultiline(t *testing.T) {
|
|
|
|
|
resp := `<soap:Envelope><soap:Body><ns2:out>
|
|
|
|
|
<ns2:string id="1">
|
|
|
|
|
<MSG>
|
|
|
|
|
<A>1</A>
|
|
|
|
|
</MSG>
|
|
|
|
|
</ns2:string>
|
|
|
|
|
</ns2:out></soap:Body></soap:Envelope>`
|
|
|
|
|
msgs := GetMsgs(resp)
|
|
|
|
|
require.Len(t, msgs, 1)
|
|
|
|
|
assert.Contains(t, msgs[0], "<MSG>")
|
|
|
|
|
assert.Contains(t, msgs[0], "<A>1</A>")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestGetErrMsg_NoError(t *testing.T) {
|
|
|
|
|
errMsg := GetErrMsg(SendOk)
|
|
|
|
|
assert.Equal(t, "", errMsg)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestGetErrMsg_Empty(t *testing.T) {
|
|
|
|
|
errMsg := GetErrMsg("<soap><body></body></soap>")
|
|
|
|
|
assert.Equal(t, "", errMsg)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestGetErrMsg_AttributesWhitespaceAndMultiline(t *testing.T) {
|
|
|
|
|
resp := `<soap:Envelope><soap:Body><soap:Fault>
|
|
|
|
|
<errorMessage xmlns="http://msg.ciims.bhia.itdcl.com" code="20">
|
|
|
|
|
Can not find
|
|
|
|
|
the event
|
|
|
|
|
</errorMessage>
|
|
|
|
|
</soap:Fault></soap:Body></soap:Envelope>`
|
|
|
|
|
errMsg := GetErrMsg(resp)
|
|
|
|
|
assert.Contains(t, errMsg, "Can not find")
|
|
|
|
|
assert.Contains(t, errMsg, "the event")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestCreateSend_XMLEscapes(t *testing.T) {
|
|
|
|
|
result := CreateSend("user<>&\"'", "pass", 0, "event", false, "<MSG/>")
|
|
|
|
|
assert.Contains(t, result, "user<>&"'")
|
|
|
|
|
assert.Contains(t, result, "pass")
|
|
|
|
|
assert.Contains(t, result, "<MSG/>")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestCreateReceive_XMLEscapes(t *testing.T) {
|
|
|
|
|
result := CreateReceive("user<>&\"'", "pass>", 2)
|
|
|
|
|
assert.Contains(t, result, "user<>&"'")
|
|
|
|
|
assert.Contains(t, result, "pass>")
|
|
|
|
|
}
|
2026-07-08 16:23:00 +08:00
|
|
|
|
|
|
|
|
func TestClientSend_ContextCanceled(t *testing.T) {
|
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
|
cancel()
|
|
|
|
|
|
|
|
|
|
client, err := NewClient(10)
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
_, err = client.Send(ctx, "http://127.0.0.1:1/nonexistent", "<dummy>")
|
|
|
|
|
assert.ErrorIs(t, err, context.Canceled)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestClientSend_ResponseTooLarge(t *testing.T) {
|
|
|
|
|
ciims := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
w.Header().Set("Content-Type", "text/xml; charset=UTF-8")
|
|
|
|
|
_, _ = io.Copy(w, io.LimitReader(zeroReader{}, maxResponseBytes+1))
|
|
|
|
|
}))
|
|
|
|
|
defer ciims.Close()
|
|
|
|
|
|
|
|
|
|
client, err := NewClient(10)
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
resp, err := client.Send(context.Background(), ciims.URL+"/services/ExchangeService", "<dummy>")
|
|
|
|
|
require.Error(t, err)
|
|
|
|
|
assert.Nil(t, resp)
|
|
|
|
|
|
|
|
|
|
var tooLarge *ResponseTooLargeError
|
|
|
|
|
assert.True(t, errors.As(err, &tooLarge))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestNewClient_ZeroOrNegativeTimeout(t *testing.T) {
|
|
|
|
|
_, err := NewClient(0)
|
|
|
|
|
require.Error(t, err)
|
|
|
|
|
assert.Contains(t, err.Error(), "positive")
|
|
|
|
|
|
|
|
|
|
_, err = NewClient(-1)
|
|
|
|
|
require.Error(t, err)
|
|
|
|
|
assert.Contains(t, err.Error(), "positive")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestNewClient_ValidTimeout(t *testing.T) {
|
|
|
|
|
client, err := NewClient(30)
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
require.NotNil(t, client)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestHTTPStatusError_Truncation(t *testing.T) {
|
|
|
|
|
body := strings.Repeat("x", 600)
|
|
|
|
|
err := &HTTPStatusError{StatusCode: 500, Status: "500 Internal Server Error", Body: body}
|
|
|
|
|
errStr := err.Error()
|
|
|
|
|
assert.Contains(t, errStr, "...")
|
|
|
|
|
assert.LessOrEqual(t, len(errStr), 570) // 46 prefix + 512 body + 3 ellipsis = 561
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestHTTPStatusError_EmptyBody(t *testing.T) {
|
|
|
|
|
err := &HTTPStatusError{StatusCode: 502, Status: "502 Bad Gateway", Body: ""}
|
|
|
|
|
assert.Equal(t, "ciims returned 502 Bad Gateway", err.Error())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestResponseTooLargeError_Error(t *testing.T) {
|
|
|
|
|
err := &ResponseTooLargeError{Limit: 100}
|
|
|
|
|
assert.Equal(t, "ciims response exceeds 100 bytes", err.Error())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestCIIMSResponse_NilReceiver(t *testing.T) {
|
|
|
|
|
var nilResp *CIIMSResponse
|
|
|
|
|
assert.False(t, nilResp.IsFault())
|
|
|
|
|
assert.Equal(t, "", nilResp.ErrorMessage())
|
|
|
|
|
assert.Nil(t, nilResp.Messages())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestClientSend_SOAPFaultWithHTTP500(t *testing.T) {
|
|
|
|
|
ciims := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
w.Header().Set("Content-Type", "text/xml; charset=UTF-8")
|
|
|
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
|
|
|
fmt.Fprint(w, ErrMsg)
|
|
|
|
|
}))
|
|
|
|
|
defer ciims.Close()
|
|
|
|
|
|
|
|
|
|
client, err := NewClient(10)
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
resp, err := client.Send(context.Background(), ciims.URL+"/services/ExchangeService", "<dummy>")
|
|
|
|
|
require.Error(t, err)
|
|
|
|
|
require.NotNil(t, resp)
|
|
|
|
|
|
|
|
|
|
var statusErr *HTTPStatusError
|
|
|
|
|
require.True(t, errors.As(err, &statusErr))
|
|
|
|
|
assert.Equal(t, http.StatusInternalServerError, statusErr.StatusCode)
|
|
|
|
|
assert.True(t, resp.IsFault())
|
|
|
|
|
assert.Equal(t, "Can not find the event [FLOP-ESTT-ATC-ALL1]", resp.ErrorMessage())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestXmlElementTexts_MalformedXML(t *testing.T) {
|
|
|
|
|
msgs := GetMsgs("<soap><Body><string>incomplete")
|
|
|
|
|
assert.Equal(t, 0, len(msgs))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type zeroReader struct{}
|
|
|
|
|
|
|
|
|
|
func (zeroReader) Read(p []byte) (int, error) {
|
|
|
|
|
for i := range p {
|
|
|
|
|
p[i] = 'a'
|
|
|
|
|
}
|
|
|
|
|
return len(p), nil
|
|
|
|
|
}
|