package namecheap
import (
"fmt"
"net/http"
"net/http/httptest"
"net/url"
"testing"
"time"
)
var (
fakeUser = "foo"
fakeKey = "bar"
fakeClientIP = "10.0.0.1"
tlds = map[string]string{
"com.au": "com.au",
"com": "com",
"co.uk": "co.uk",
"uk": "uk",
"edu": "edu",
"co.com": "co.com",
"za.com": "za.com",
}
)
func assertEq(t *testing.T, variable, got, want string) {
if got != want {
t.Errorf("Expected %s to be '%s' but got '%s'", variable, want, got)
}
}
func assertHdr(tc *testcase, t *testing.T, values *url.Values) {
ch, _ := newChallenge(tc.domain, "", tlds)
assertEq(t, "ApiUser", values.Get("ApiUser"), fakeUser)
assertEq(t, "ApiKey", values.Get("ApiKey"), fakeKey)
assertEq(t, "UserName", values.Get("UserName"), fakeUser)
assertEq(t, "ClientIp", values.Get("ClientIp"), fakeClientIP)
assertEq(t, "SLD", values.Get("SLD"), ch.sld)
assertEq(t, "TLD", values.Get("TLD"), ch.tld)
}
func mockServer(tc *testcase, t *testing.T, w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
values := r.URL.Query()
cmd := values.Get("Command")
switch cmd {
case "namecheap.domains.dns.getHosts":
assertHdr(tc, t, &values)
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, tc.getHostsResponse)
case "namecheap.domains.getTldList":
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, responseGetTlds)
default:
t.Errorf("Unexpected GET command: %s", cmd)
}
case http.MethodPost:
r.ParseForm()
values := r.Form
cmd := values.Get("Command")
switch cmd {
case "namecheap.domains.dns.setHosts":
assertHdr(tc, t, &values)
w.WriteHeader(http.StatusOK)
fmt.Fprint(w, tc.setHostsResponse)
default:
t.Errorf("Unexpected POST command: %s", cmd)
}
default:
t.Errorf("Unexpected http method: %s", r.Method)
}
}
func testGetHosts(tc *testcase, t *testing.T) {
mock := httptest.NewServer(http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
mockServer(tc, t, w, r)
}))
defer mock.Close()
prov := &DNSProvider{
baseURL: mock.URL,
apiUser: fakeUser,
apiKey: fakeKey,
clientIP: fakeClientIP,
client: &http.Client{Timeout: 60 * time.Second},
}
ch, _ := newChallenge(tc.domain, "", tlds)
hosts, err := prov.getHosts(ch)
if tc.errString != "" {
if err == nil || err.Error() != tc.errString {
t.Errorf("Namecheap getHosts case %s expected error", tc.name)
}
} else {
if err != nil {
t.Errorf("Namecheap getHosts case %s failed\n%v", tc.name, err)
}
}
next1:
for _, h := range hosts {
for _, th := range tc.hosts {
if h == th {
continue next1
}
}
t.Errorf("getHosts case %s unexpected record [%s:%s:%s]",
tc.name, h.Type, h.Name, h.Address)
}
next2:
for _, th := range tc.hosts {
for _, h := range hosts {
if h == th {
continue next2
}
}
t.Errorf("getHosts case %s missing record [%s:%s:%s]",
tc.name, th.Type, th.Name, th.Address)
}
}
func mockDNSProvider(url string) *DNSProvider {
return &DNSProvider{
baseURL: url,
apiUser: fakeUser,
apiKey: fakeKey,
clientIP: fakeClientIP,
client: &http.Client{Timeout: 60 * time.Second},
}
}
func testSetHosts(tc *testcase, t *testing.T) {
mock := httptest.NewServer(http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
mockServer(tc, t, w, r)
}))
defer mock.Close()
prov := mockDNSProvider(mock.URL)
ch, _ := newChallenge(tc.domain, "", tlds)
hosts, err := prov.getHosts(ch)
if tc.errString != "" {
if err == nil || err.Error() != tc.errString {
t.Errorf("Namecheap getHosts case %s expected error", tc.name)
}
} else {
if err != nil {
t.Errorf("Namecheap getHosts case %s failed\n%v", tc.name, err)
}
}
if err != nil {
return
}
err = prov.setHosts(ch, hosts)
if err != nil {
t.Errorf("Namecheap setHosts case %s failed", tc.name)
}
}
func testPresent(tc *testcase, t *testing.T) {
mock := httptest.NewServer(http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
mockServer(tc, t, w, r)
}))
defer mock.Close()
prov := mockDNSProvider(mock.URL)
err := prov.Present(tc.domain, "", "dummyKey")
if tc.errString != "" {
if err == nil || err.Error() != tc.errString {
t.Errorf("Namecheap Present case %s expected error", tc.name)
}
} else {
if err != nil {
t.Errorf("Namecheap Present case %s failed\n%v", tc.name, err)
}
}
}
func testCleanUp(tc *testcase, t *testing.T) {
mock := httptest.NewServer(http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
mockServer(tc, t, w, r)
}))
defer mock.Close()
prov := mockDNSProvider(mock.URL)
err := prov.CleanUp(tc.domain, "", "dummyKey")
if tc.errString != "" {
if err == nil || err.Error() != tc.errString {
t.Errorf("Namecheap CleanUp case %s expected error", tc.name)
}
} else {
if err != nil {
t.Errorf("Namecheap CleanUp case %s failed\n%v", tc.name, err)
}
}
}
func TestNamecheap(t *testing.T) {
for _, tc := range testcases {
testGetHosts(&tc, t)
testSetHosts(&tc, t)
testPresent(&tc, t)
testCleanUp(&tc, t)
}
}
func TestNamecheapDomainSplit(t *testing.T) {
tests := []struct {
domain string
valid bool
tld string
sld string
host string
}{
{"a.b.c.test.co.uk", true, "co.uk", "test", "a.b.c"},
{"test.co.uk", true, "co.uk", "test", ""},
{"test.com", true, "com", "test", ""},
{"test.co.com", true, "co.com", "test", ""},
{"www.test.com.au", true, "com.au", "test", "www"},
{"www.za.com", true, "za.com", "www", ""},
{"", false, "", "", ""},
{"a", false, "", "", ""},
{"com", false, "", "", ""},
{"co.com", false, "", "", ""},
{"co.uk", false, "", "", ""},
{"test.au", false, "", "", ""},
{"za.com", false, "", "", ""},
{"www.za", false, "", "", ""},
{"www.test.au", false, "", "", ""},
{"www.test.unk", false, "", "", ""},
}
for _, test := range tests {
test := test
t.Run(test.domain, func(t *testing.T) {
valid := true
ch, err := newChallenge(test.domain, "", tlds)
if err != nil {
valid = false
}
if test.valid && !valid {
t.Errorf("Expected '%s' to split", test.domain)
} else if !test.valid && valid {
t.Errorf("Expected '%s' to produce error", test.domain)
}
if test.valid && valid {
assertEq(t, "domain", ch.domain, test.domain)
assertEq(t, "tld", ch.tld, test.tld)
assertEq(t, "sld", ch.sld, test.sld)
assertEq(t, "host", ch.host, test.host)
}
})
}
}
type testcase struct {
name string
domain string
hosts []host
errString string
getHostsResponse string
setHostsResponse string
}
var testcases = []testcase{
{
"Test:Success:1",
"test.example.com",
[]host{
{"A", "home", "10.0.0.1", "10", "1799"},
{"A", "www", "10.0.0.2", "10", "1200"},
{"AAAA", "a", "::0", "10", "1799"},
{"CNAME", "*", "example.com.", "10", "1799"},
{"MXE", "example.com", "10.0.0.5", "10", "1800"},
{"URL", "xyz", "https://google.com", "10", "1799"},
},
"",
responseGetHostsSuccess1,
responseSetHostsSuccess1,
},
{
"Test:Success:2",
"example.com",
[]host{
{"A", "@", "10.0.0.2", "10", "1200"},
{"A", "www", "10.0.0.3", "10", "60"},
},
"",
responseGetHostsSuccess2,
responseSetHostsSuccess2,
},
{
"Test:Error:BadApiKey:1",
"test.example.com",
nil,
"Namecheap error: API Key is invalid or API access has not been enabled [1011102]",
responseGetHostsErrorBadAPIKey1,
"",
},
}
var responseGetHostsSuccess1 = `
namecheap.domains.dns.getHosts
PHX01SBAPI01
--5:00
3.338
`
var responseSetHostsSuccess1 = `
namecheap.domains.dns.setHosts
PHX01SBAPI01
--5:00
2.347
`
var responseGetHostsSuccess2 = `
namecheap.domains.dns.getHosts
PHX01SBAPI01
--5:00
3.338
`
var responseSetHostsSuccess2 = `
namecheap.domains.dns.setHosts
PHX01SBAPI01
--5:00
2.347
`
var responseGetHostsErrorBadAPIKey1 = `
API Key is invalid or API access has not been enabled
PHX01SBAPI01
--5:00
0
`
var responseGetTlds = `
namecheap.domains.getTldList
Most recognized top level domain
PHX01SBAPI01
--5:00
0.004
`