lego/providers/dns/liquidweb/servermock_test.go
Jack Hayhurst 8afdc9d01c
liquidweb: detect zone automatically (#2031)
Co-authored-by: Fernandez Ludovic <ldez@users.noreply.github.com>
2023-10-14 17:08:29 +02:00

305 lines
8.5 KiB
Go

package liquidweb
import (
"bytes"
"encoding/json"
"fmt"
"io"
"math/rand"
"net/http"
"net/http/httptest"
"testing"
"github.com/liquidweb/liquidweb-go/network"
"github.com/liquidweb/liquidweb-go/types"
)
func mockAPIServer(t *testing.T, initRecs []network.DNSRecord) string {
t.Helper()
recs := make(map[int]network.DNSRecord)
for _, rec := range initRecs {
recs[int(rec.ID)] = rec
}
mux := http.NewServeMux()
mux.Handle("/v1/Network/DNS/Record/delete", mockAPIDelete(recs))
mux.Handle("/v1/Network/DNS/Record/create", mockAPICreate(recs))
mux.Handle("/v1/Network/DNS/Zone/list", mockAPIListZones())
mux.Handle("/bleed/Network/DNS/Record/delete", mockAPIDelete(recs))
mux.Handle("/bleed/Network/DNS/Record/create", mockAPICreate(recs))
mux.Handle("/bleed/Network/DNS/Zone/list", mockAPIListZones())
server := httptest.NewServer(requireBasicAuth(requireJSON(mux)))
t.Cleanup(server.Close)
return server.URL
}
func requireBasicAuth(next http.Handler) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
username, password, ok := r.BasicAuth()
if ok && username == "blars" && password == "tacoman" {
next.ServeHTTP(w, r)
return
}
http.Error(w, "invalid auth", http.StatusForbidden)
}
}
func requireJSON(next http.Handler) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
buf := &bytes.Buffer{}
_, err := buf.ReadFrom(r.Body)
if err != nil {
http.Error(w, "malformed request - json required", http.StatusBadRequest)
return
}
r.Body = io.NopCloser(buf)
next.ServeHTTP(w, r)
}
}
func mockAPICreate(recs map[int]network.DNSRecord) http.HandlerFunc {
_, mockAPIServerZones := makeMockZones()
return func(w http.ResponseWriter, r *http.Request) {
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "invalid request", http.StatusInternalServerError)
return
}
req := struct {
Params network.DNSRecord `json:"params"`
}{}
if err = json.Unmarshal(body, &req); err != nil {
http.Error(w, makeEncodingError(body), http.StatusBadRequest)
return
}
req.Params.ID = types.FlexInt(rand.Intn(10000000))
req.Params.ZoneID = types.FlexInt(mockAPIServerZones[req.Params.Name])
if _, exists := recs[int(req.Params.ID)]; exists {
http.Error(w, "dns record already exists", http.StatusTeapot)
return
}
recs[int(req.Params.ID)] = req.Params
resp, err := json.Marshal(req.Params)
if err != nil {
http.Error(w, "", http.StatusInternalServerError)
return
}
http.Error(w, string(resp), http.StatusOK)
}
}
func mockAPIDelete(recs map[int]network.DNSRecord) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "invalid request", http.StatusInternalServerError)
return
}
req := struct {
Params struct {
Name string `json:"name"`
ID int `json:"id"`
} `json:"params"`
}{}
if err := json.Unmarshal(body, &req); err != nil {
http.Error(w, makeEncodingError(body), http.StatusBadRequest)
return
}
if req.Params.ID == 0 {
http.Error(w, `{"error":"","error_class":"LW::Exception::Input::Multiple","errors":[{"error":"","error_class":"LW::Exception::Input::Required","field":"id","full_message":"The required field 'id' was missing a value.","position":null}],"field":["id"],"full_message":"The following input errors occurred:\nThe required field 'id' was missing a value.","type":null}`, http.StatusOK)
return
}
if _, ok := recs[req.Params.ID]; !ok {
http.Error(w, fmt.Sprintf(`{"error":"","error_class":"LW::Exception::RecordNotFound","field":"network_dns_rr","full_message":"Record 'network_dns_rr: %d' not found","input":"%d","public_message":null}`, req.Params.ID, req.Params.ID), http.StatusOK)
return
}
delete(recs, req.Params.ID)
http.Error(w, fmt.Sprintf("{\"deleted\":%d}", req.Params.ID), http.StatusOK)
}
}
func mockAPIListZones() http.HandlerFunc {
mockZones, mockAPIServerZones := makeMockZones()
return func(w http.ResponseWriter, r *http.Request) {
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "invalid request", http.StatusInternalServerError)
return
}
req := struct {
Params struct {
PageNum int `json:"page_num"`
} `json:"params"`
}{}
if err = json.Unmarshal(body, &req); err != nil {
http.Error(w, makeEncodingError(body), http.StatusBadRequest)
return
}
switch {
case req.Params.PageNum < 1:
req.Params.PageNum = 1
case req.Params.PageNum > len(mockZones):
req.Params.PageNum = len(mockZones)
}
resp := mockZones[req.Params.PageNum]
resp.ItemTotal = types.FlexInt(len(mockAPIServerZones))
resp.PageNum = types.FlexInt(req.Params.PageNum)
resp.PageSize = 5
resp.PageTotal = types.FlexInt(len(mockZones))
var respBody []byte
if respBody, err = json.Marshal(resp); err == nil {
http.Error(w, string(respBody), http.StatusOK)
return
}
http.Error(w, "", http.StatusInternalServerError)
}
}
func makeEncodingError(buf []byte) string {
return fmt.Sprintf(`{"data":"%q","encoding":"JSON","error":"unexpected end of string while parsing JSON string, at character offset 32 (before \"(end of string)\") at /usr/local/lp/libs/perl/LW/Base/Role/Serializer.pm line 16.\n","error_class":"LW::Exception::Deserialize","full_message":"Could not deserialize \"%q\" from JSON: unexpected end of string while parsing JSON string, at character offset 32 (before \"(end of string)\") at /usr/local/lp/libs/perl/LW/Base/Role/Serializer.pm line 16.\n"}⏎`, string(buf), string(buf))
}
func makeMockZones() (map[int]network.DNSZoneList, map[string]int) {
mockZones := map[int]network.DNSZoneList{
1: {
Items: []network.DNSZone{
{
ID: 1,
Name: "blars.com",
Active: 1,
DelegationStatus: "CORRECT",
PrimaryNameserver: "ns.liquidweb.com",
},
{
ID: 2,
Name: "tacoman.com",
Active: 1,
DelegationStatus: "CORRECT",
PrimaryNameserver: "ns.liquidweb.com",
},
{
ID: 3,
Name: "storm.com",
Active: 1,
DelegationStatus: "CORRECT",
PrimaryNameserver: "ns.liquidweb.com",
},
{
ID: 4,
Name: "not-apple.com",
Active: 1,
DelegationStatus: "BAD_NAMESERVERS",
PrimaryNameserver: "ns.liquidweb.com",
},
{
ID: 5,
Name: "example.com",
Active: 1,
DelegationStatus: "BAD_NAMESERVERS",
PrimaryNameserver: "ns.liquidweb.com",
},
},
},
2: {
Items: []network.DNSZone{
{
ID: 6,
Name: "banana.com",
Active: 1,
DelegationStatus: "NXDOMAIN",
PrimaryNameserver: "ns.liquidweb.com",
},
{
ID: 7,
Name: "cherry.com",
Active: 1,
DelegationStatus: "SERVFAIL",
PrimaryNameserver: "ns.liquidweb.com",
},
{
ID: 8,
Name: "dates.com",
Active: 1,
DelegationStatus: "SERVFAIL",
PrimaryNameserver: "ns.liquidweb.com",
},
{
ID: 9,
Name: "eggplant.com",
Active: 1,
DelegationStatus: "SERVFAIL",
PrimaryNameserver: "ns.liquidweb.com",
},
{
ID: 10,
Name: "fig.com",
Active: 1,
DelegationStatus: "UNKNOWN",
PrimaryNameserver: "ns.liquidweb.com",
},
},
},
3: {
Items: []network.DNSZone{
{
ID: 11,
Name: "grapes.com",
Active: 1,
DelegationStatus: "UNKNOWN",
PrimaryNameserver: "ns.liquidweb.com",
},
{
ID: 12,
Name: "money.banana.com",
Active: 1,
DelegationStatus: "UNKNOWN",
PrimaryNameserver: "ns.liquidweb.com",
},
{
ID: 13,
Name: "money.stand.banana.com",
Active: 1,
DelegationStatus: "UNKNOWN",
PrimaryNameserver: "ns.liquidweb.com",
},
{
ID: 14,
Name: "stand.banana.com",
Active: 1,
DelegationStatus: "UNKNOWN",
PrimaryNameserver: "ns.liquidweb.com",
},
},
},
}
mockAPIServerZones := make(map[string]int)
for _, page := range mockZones {
for _, zone := range page.Items {
mockAPIServerZones[zone.Name] = int(zone.ID)
}
}
return mockZones, mockAPIServerZones
}