lego/providers/dns/loopia/internal/client_test.go
2025-08-04 14:46:37 +02:00

294 lines
6.7 KiB
Go

package internal
import (
"encoding/xml"
"net/http"
"net/http/httptest"
"testing"
"github.com/go-acme/lego/v4/platform/tester/servermock"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func mockBuilder(password string) *servermock.Builder[*Client] {
return servermock.NewBuilder[*Client](
func(server *httptest.Server) (*Client, error) {
client := NewClient("apiuser", password)
client.HTTPClient = server.Client()
client.BaseURL = server.URL + "/"
return client, nil
},
servermock.CheckHeader().WithContentType("text/xml"),
)
}
func TestClient_AddZoneRecord(t *testing.T) {
testCases := []struct {
desc string
password string
domain string
request string
response string
err string
}{
{
desc: "auth ok",
password: "goodpassword",
domain: exampleDomain,
request: addZoneRecordGoodAuth,
response: responseOk,
},
{
desc: "auth error",
password: "badpassword",
domain: exampleDomain,
request: addZoneRecordBadAuth,
response: responseAuthError,
err: "authentication error",
},
{
desc: "unknown error",
password: "goodpassword",
domain: "badexample.com",
request: addZoneRecordNonValidDomain,
response: responseUnknownError,
err: `unknown error: "UNKNOWN_ERROR"`,
},
{
desc: "empty response",
password: "goodpassword",
domain: "empty.com",
request: addZoneRecordEmptyResponse,
response: "",
err: "unmarshal error: EOF",
},
}
for _, test := range testCases {
t.Run(test.desc, func(t *testing.T) {
client := mockBuilder(test.password).
Route("POST /",
servermock.RawStringResponse(test.response),
servermock.CheckRequestBody(test.request)).
Build(t)
err := client.AddTXTRecord(t.Context(), test.domain, exampleSubDomain, 123, "TXTrecord")
if test.err == "" {
require.NoError(t, err)
} else {
require.Error(t, err)
assert.EqualError(t, err, test.err)
}
})
}
}
func TestClient_RemoveSubdomain(t *testing.T) {
testCases := []struct {
desc string
password string
domain string
request string
response string
err string
}{
{
desc: "auth ok",
password: "goodpassword",
domain: exampleDomain,
request: removeSubdomainGoodAuth,
response: responseOk,
},
{
desc: "auth error",
password: "badpassword",
domain: exampleDomain,
request: removeSubdomainBadAuth,
response: responseAuthError,
err: "authentication error",
},
{
desc: "unknown error",
password: "goodpassword",
domain: "badexample.com",
request: removeSubdomainNonValidDomain,
response: responseUnknownError,
err: `unknown error: "UNKNOWN_ERROR"`,
},
{
desc: "empty response",
password: "goodpassword",
domain: "empty.com",
request: removeSubdomainEmptyResponse,
response: "",
err: "unmarshal error: EOF",
},
}
for _, test := range testCases {
t.Run(test.desc, func(t *testing.T) {
client := mockBuilder(test.password).
Route("POST /",
servermock.RawStringResponse(test.response),
servermock.CheckRequestBody(test.request)).
Build(t)
err := client.RemoveSubdomain(t.Context(), test.domain, exampleSubDomain)
if test.err == "" {
require.NoError(t, err)
} else {
require.Error(t, err)
assert.EqualError(t, err, test.err)
}
})
}
}
func TestClient_RemoveZoneRecord(t *testing.T) {
testCases := []struct {
desc string
password string
domain string
request string
response string
err string
}{
{
desc: "auth ok",
password: "goodpassword",
domain: exampleDomain,
request: removeRecordGoodAuth,
response: responseOk,
},
{
desc: "auth error",
password: "badpassword",
domain: exampleDomain,
request: removeRecordBadAuth,
response: responseAuthError,
err: "authentication error",
},
{
desc: "uknown error",
password: "goodpassword",
domain: "badexample.com",
request: removeRecordNonValidDomain,
response: responseUnknownError,
err: `unknown error: "UNKNOWN_ERROR"`,
},
{
desc: "empty response",
password: "goodpassword",
domain: "empty.com",
request: removeRecordEmptyResponse,
response: "",
err: "unmarshal error: EOF",
},
}
for _, test := range testCases {
t.Run(test.desc, func(t *testing.T) {
client := mockBuilder(test.password).
Route("POST /",
servermock.RawStringResponse(test.response),
servermock.CheckRequestBody(test.request)).
Build(t)
err := client.RemoveTXTRecord(t.Context(), test.domain, exampleSubDomain, 12345678)
if test.err == "" {
require.NoError(t, err)
} else {
require.Error(t, err)
assert.EqualError(t, err, test.err)
}
})
}
}
func TestClient_GetZoneRecord(t *testing.T) {
client := mockBuilder("goodpassword").
Route("POST /",
servermock.RawStringResponse(getZoneRecordsResponse),
servermock.CheckRequestBody(getZoneRecords)).
Build(t)
recordObjs, err := client.GetTXTRecords(t.Context(), exampleDomain, exampleSubDomain)
require.NoError(t, err)
expected := []RecordObj{
{
Type: "TXT",
TTL: 300,
Priority: 0,
Rdata: exampleRdata,
RecordID: 12345678,
},
}
assert.Equal(t, expected, recordObjs)
}
func TestClient_rpcCall_404(t *testing.T) {
client := mockBuilder("apipassword").
Route("POST /",
servermock.RawStringResponse("<?xml version='1.0' encoding='UTF-8'?>").
WithStatusCode(http.StatusNotFound)).
Build(t)
call := &methodCall{
MethodName: "dummyMethod",
Params: []param{
paramString{Value: "test1"},
},
}
err := client.rpcCall(t.Context(), call, &responseString{})
require.EqualError(t, err, "unexpected status code: [status code: 404] body: <?xml version='1.0' encoding='UTF-8'?>")
}
func TestClient_rpcCall_RPCError(t *testing.T) {
client := mockBuilder("apipassword").
Route("POST /",
servermock.RawStringResponse(responseRPCError)).
Build(t)
call := &methodCall{
MethodName: "getDomains",
Params: []param{
paramString{Value: "test1"},
},
}
err := client.rpcCall(t.Context(), call, &responseString{})
require.EqualError(t, err, "RPC Error: (201) Method signature error: 42")
}
func TestUnmarshallFaultyRecordObject(t *testing.T) {
testCases := []struct {
desc string
xml string
}{
{
desc: "faulty name",
xml: "<name>name<name>",
},
{
desc: "faulty string",
xml: "<value><string>foo<string></value>",
},
{
desc: "faulty int",
xml: "<value><int>1<int></value>",
},
}
for _, test := range testCases {
t.Run(test.desc, func(t *testing.T) {
resp := &RecordObj{}
err := xml.Unmarshal([]byte(test.xml), resp)
require.Error(t, err)
})
}
}