lego/providers/dns/httpreq/httpreq_test.go
2025-08-04 14:46:37 +02:00

270 lines
7.2 KiB
Go

package httpreq
import (
"net/http/httptest"
"net/url"
"testing"
"github.com/go-acme/lego/v4/platform/tester"
"github.com/go-acme/lego/v4/platform/tester/servermock"
"github.com/stretchr/testify/require"
)
var envTest = tester.NewEnvTest(EnvEndpoint, EnvMode, EnvUsername, EnvPassword)
func TestNewDNSProvider(t *testing.T) {
testCases := []struct {
desc string
envVars map[string]string
expected string
}{
{
desc: "success",
envVars: map[string]string{
EnvEndpoint: "http://localhost:8090",
},
},
{
desc: "invalid URL",
envVars: map[string]string{
EnvEndpoint: ":",
},
expected: `httpreq: parse ":": missing protocol scheme`,
},
{
desc: "missing endpoint",
envVars: map[string]string{
EnvEndpoint: "",
},
expected: "httpreq: some credentials information are missing: HTTPREQ_ENDPOINT",
},
}
for _, test := range testCases {
t.Run(test.desc, func(t *testing.T) {
defer envTest.RestoreEnv()
envTest.ClearEnv()
envTest.Apply(test.envVars)
p, err := NewDNSProvider()
if test.expected == "" {
require.NoError(t, err)
require.NotNil(t, p)
require.NotNil(t, p.config)
} else {
require.EqualError(t, err, test.expected)
}
})
}
}
func TestNewDNSProviderConfig(t *testing.T) {
testCases := []struct {
desc string
endpoint *url.URL
expected string
}{
{
desc: "success",
endpoint: mustParse("http://localhost:8090"),
},
{
desc: "missing endpoint",
expected: "httpreq: the endpoint is missing",
},
}
for _, test := range testCases {
t.Run(test.desc, func(t *testing.T) {
config := NewDefaultConfig()
config.Endpoint = test.endpoint
p, err := NewDNSProviderConfig(config)
if test.expected == "" {
require.NoError(t, err)
require.NotNil(t, p)
require.NotNil(t, p.config)
} else {
require.EqualError(t, err, test.expected)
}
})
}
}
func TestNewDNSProvider_Present(t *testing.T) {
envTest.RestoreEnv()
testCases := []struct {
desc string
builder *servermock.Builder[*DNSProvider]
expectedError string
}{
{
desc: "success",
builder: mockBuilder("").
Route("/present",
servermock.RawStringResponse("lego"),
servermock.CheckRequestJSONBody(`{"fqdn":"_acme-challenge.domain.","value":"LHDhK3oGRvkiefQnx7OOczTY5Tic_xZ6HcMOc_gmtoM"}`)),
},
{
desc: "success with path prefix",
builder: mockBuilderWithPathPrefix("", "/api/acme/").
Route("/api/acme/present",
servermock.RawStringResponse("lego"),
servermock.CheckRequestJSONBody(`{"fqdn":"_acme-challenge.domain.","value":"LHDhK3oGRvkiefQnx7OOczTY5Tic_xZ6HcMOc_gmtoM"}`)),
},
{
desc: "error",
builder: mockBuilder(""),
expectedError: "httpreq: unexpected status code: [status code: 404] body: 404 page not found",
},
{
desc: "success raw mode",
builder: mockBuilder("RAW").
Route("/present",
servermock.RawStringResponse("lego"),
servermock.CheckRequestBody(`{"domain":"domain","token":"token","keyAuth":"key"}`)),
},
{
desc: "error raw mode",
builder: mockBuilder("RAW"),
expectedError: "httpreq: unexpected status code: [status code: 404] body: 404 page not found",
},
{
desc: "basic auth fail",
builder: mockBuilderWithBasicAuth("nope", "nope").
Route("/present", servermock.Noop()),
expectedError: `httpreq: unexpected status code: [status code: 400] body: invalid credentials: got [username: "nope", password: "nope"], want [username: "user", password: "secret"]`,
},
{
desc: "basic auth success",
builder: mockBuilderWithBasicAuth("user", "secret").
Route("/present",
servermock.RawStringResponse("lego"),
servermock.CheckRequestJSONBody(`{"fqdn":"_acme-challenge.domain.","value":"LHDhK3oGRvkiefQnx7OOczTY5Tic_xZ6HcMOc_gmtoM"}`)),
},
}
for _, test := range testCases {
t.Run(test.desc, func(t *testing.T) {
p := test.builder.Build(t)
err := p.Present("domain", "token", "key")
if test.expectedError == "" {
require.NoError(t, err)
} else {
require.EqualError(t, err, test.expectedError)
}
})
}
}
func TestNewDNSProvider_Cleanup(t *testing.T) {
envTest.RestoreEnv()
testCases := []struct {
desc string
builder *servermock.Builder[*DNSProvider]
expectedError string
}{
{
desc: "success",
builder: mockBuilder("").
Route("/cleanup",
servermock.RawStringResponse("lego"),
servermock.CheckRequestJSONBody(`{"fqdn":"_acme-challenge.domain.","value":"LHDhK3oGRvkiefQnx7OOczTY5Tic_xZ6HcMOc_gmtoM"}`)),
},
{
desc: "error",
builder: mockBuilder(""),
expectedError: "httpreq: unexpected status code: [status code: 404] body: 404 page not found",
},
{
desc: "success raw mode",
builder: mockBuilder("RAW").
Route("/cleanup",
servermock.RawStringResponse("lego"),
servermock.CheckRequestBody(`{"domain":"domain","token":"token","keyAuth":"key"}`)),
},
{
desc: "error raw mode",
builder: mockBuilder("RAW"),
expectedError: "httpreq: unexpected status code: [status code: 404] body: 404 page not found",
},
{
desc: "basic auth fail",
builder: mockBuilderWithBasicAuth("test", "example").
Route("/cleanup", servermock.Noop()),
expectedError: `httpreq: unexpected status code: [status code: 400] body: invalid credentials: got [username: "test", password: "example"], want [username: "user", password: "secret"]`,
},
{
desc: "basic auth success",
builder: mockBuilderWithBasicAuth("user", "secret").
Route("/cleanup",
servermock.RawStringResponse("lego"),
servermock.CheckRequestJSONBody(`{"fqdn":"_acme-challenge.domain.","value":"LHDhK3oGRvkiefQnx7OOczTY5Tic_xZ6HcMOc_gmtoM"}`)),
},
}
for _, test := range testCases {
t.Run(test.desc, func(t *testing.T) {
p := test.builder.Build(t)
err := p.CleanUp("domain", "token", "key")
if test.expectedError == "" {
require.NoError(t, err)
} else {
require.EqualError(t, err, test.expectedError)
}
})
}
}
func mockBuilder(mode string) *servermock.Builder[*DNSProvider] {
return servermock.NewBuilder(
func(server *httptest.Server) (*DNSProvider, error) {
config := NewDefaultConfig()
config.HTTPClient = server.Client()
config.Endpoint, _ = url.Parse(server.URL)
config.Mode = mode
return NewDNSProviderConfig(config)
})
}
func mockBuilderWithPathPrefix(mode, prefix string) *servermock.Builder[*DNSProvider] {
return servermock.NewBuilder(
func(server *httptest.Server) (*DNSProvider, error) {
config := NewDefaultConfig()
config.HTTPClient = server.Client()
config.Endpoint, _ = url.Parse(server.URL + prefix)
config.Mode = mode
return NewDNSProviderConfig(config)
})
}
func mockBuilderWithBasicAuth(username, password string) *servermock.Builder[*DNSProvider] {
return servermock.NewBuilder(
func(server *httptest.Server) (*DNSProvider, error) {
config := NewDefaultConfig()
config.HTTPClient = server.Client()
config.Endpoint, _ = url.Parse(server.URL)
config.Username = username
config.Password = password
return NewDNSProviderConfig(config)
},
servermock.CheckHeader().WithBasicAuth("user", "secret"))
}
func mustParse(rawURL string) *url.URL {
uri, err := url.Parse(rawURL)
if err != nil {
panic(err)
}
return uri
}