Skip to content

Commit

Permalink
review: 5
Browse files Browse the repository at this point in the history
  • Loading branch information
ldez committed Apr 12, 2023
1 parent caf1120 commit 5a203b9
Show file tree
Hide file tree
Showing 8 changed files with 528 additions and 130 deletions.
35 changes: 30 additions & 5 deletions providers/dns/nicru/internal/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"encoding/xml"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"strconv"
Expand All @@ -20,6 +21,19 @@ const tokenURL = defaultBaseURL + "/oauth/token"

const successStatus = "success"

// Trimmer trim all XML fields.
type Trimmer struct {
decoder *xml.Decoder
}

func (tr Trimmer) Token() (xml.Token, error) {
t, err := tr.decoder.Token()
if cd, ok := t.(xml.CharData); ok {
t = xml.CharData(bytes.TrimSpace(cd))
}
return t, err
}

// OauthConfiguration credentials.
type OauthConfiguration struct {
OAuth2ClientID string
Expand Down Expand Up @@ -77,7 +91,7 @@ func NewClient(httpClient *http.Client, serviceName string) (*Client, error) {
}, nil
}

func (c *Client) GetZones() ([]*Zone, error) {
func (c *Client) GetZones() ([]Zone, error) {
endpoint := c.baseURL.JoinPath("dns-master", "services", c.serviceName, "zones")

req, err := http.NewRequest(http.MethodGet, endpoint.String(), nil)
Expand All @@ -93,7 +107,7 @@ func (c *Client) GetZones() ([]*Zone, error) {
return apiResponse.Data.Zone, nil
}

func (c *Client) GetRecords(fqdn string) ([]*RR, error) {
func (c *Client) GetRecords(fqdn string) ([]RR, error) {
endpoint := c.baseURL.JoinPath("dns-master", "services", c.serviceName, "zones", fqdn, "records")

req, err := http.NewRequest(http.MethodGet, endpoint.String(), nil)
Expand All @@ -106,7 +120,7 @@ func (c *Client) GetRecords(fqdn string) ([]*RR, error) {
return nil, err
}

var records []*RR
var records []RR
for _, zone := range apiResponse.Data.Zone {
records = append(records, zone.RR...)
}
Expand All @@ -115,7 +129,7 @@ func (c *Client) GetRecords(fqdn string) ([]*RR, error) {
}

func (c *Client) AddTxtRecord(zoneName string, name string, content string, ttl int) (*Response, error) {
request := &Request{RRList: &RrList{RR: []*RR{{
request := &Request{RRList: &RrList{RR: []RR{{
Name: name,
TTL: strconv.Itoa(ttl),
Type: "TXT",
Expand Down Expand Up @@ -155,6 +169,9 @@ func (c *Client) addRecords(zoneName string, request *Request) (*Response, error
return nil, err
}

// PUT https://api.nic.ru/dns-master/services/<service_id>/zones/<zone_name>/records
// PUT https://api.nic.ru/dns-master/services/TESTSERVICE/zones/TEST.RU/records

req, err := http.NewRequest(http.MethodPut, endpoint.String(), body)
if err != nil {
return nil, err
Expand All @@ -168,15 +185,23 @@ func (c *Client) do(req *http.Request) (*Response, error) {
if err != nil {
return nil, err
}

defer func() { _ = resp.Body.Close() }()

apiResponse := &Response{}

err = xml.NewDecoder(resp.Body).Decode(apiResponse)
raw, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}

decoder := xml.NewTokenDecoder(Trimmer{decoder: xml.NewDecoder(bytes.NewReader(raw))})

err = decoder.Decode(apiResponse)
if err != nil {
return nil, fmt.Errorf("[status code=%d] %s", resp.StatusCode, string(raw))
}

if apiResponse.Status != successStatus {
return nil, apiResponse.Errors.Error
}
Expand Down
286 changes: 286 additions & 0 deletions providers/dns/nicru/internal/client_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,286 @@
package internal

import (
"encoding/xml"
"fmt"
"io"
"net/http"
"net/http/httptest"
"net/url"
"os"
"path/filepath"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func setupTest(t *testing.T, pattern string, handler http.HandlerFunc) *Client {
t.Helper()

mux := http.NewServeMux()
server := httptest.NewServer(mux)
t.Cleanup(server.Close)

mux.HandleFunc(pattern, handler)

client, err := NewClient(server.Client(), "test")
require.NoError(t, err)

client.baseURL, _ = url.Parse(server.URL)

return client
}

func writeFixtures(method string, filename string, status int) http.HandlerFunc {
return func(rw http.ResponseWriter, req *http.Request) {
if req.Method != method {
http.Error(rw, fmt.Sprintf("unsupported method: %s", req.Method), http.StatusMethodNotAllowed)
return
}

file, err := os.Open(filepath.Join("fixtures", filename))
if err != nil {
http.Error(rw, err.Error(), http.StatusInternalServerError)
return
}

defer func() { _ = file.Close() }()

rw.WriteHeader(status)
_, err = io.Copy(rw, file)
if err != nil {
http.Error(rw, err.Error(), http.StatusInternalServerError)
return
}
}
}

func TestClient_GetZones(t *testing.T) {
client := setupTest(t, "/dns-master/services/test/zones",
writeFixtures(http.MethodGet, "zones_GET.xml", http.StatusOK))

zones, err := client.GetZones()
require.NoError(t, err)

expected := []Zone{
{
Admin: "123/NIC-REG",
Enable: "true",
HasChanges: "false",
HasPrimary: "true",
ID: "227645",
IdnName: "тест.рф",
Name: "xn—e1aybc.xn--p1ai",
Payer: "123/NIC-REG",
Service: "myservice",
},
{
Admin: "123/NIC-REG",
Enable: "true",
HasChanges: "false",
HasPrimary: "true",
ID: "227642",
IdnName: "example.ru",
Name: "example.ru",
Payer: "123/NIC-REG",
Service: "myservice",
},
{
Admin: "123/NIC-REG",
Enable: "true",
HasChanges: "false",
HasPrimary: "true",
ID: "227643",
IdnName: "test.su",
Name: "test.su",
Payer: "123/NIC-REG",
Service: "myservice",
},
}

assert.Equal(t, expected, zones)
}

func TestClient_GetZones_error(t *testing.T) {
client := setupTest(t, "/dns-master/services/test/zones",
writeFixtures(http.MethodGet, "errors.xml", http.StatusOK))

_, err := client.GetZones()
require.ErrorIs(t, err, Error{
Text: "Access token expired or not found",
Code: "4097",
})
}

func TestClient_GetRecords(t *testing.T) {
client := setupTest(t, "/dns-master/services/test/zones/example.com./records",
writeFixtures(http.MethodGet, "records_GET.xml", http.StatusOK))

records, err := client.GetRecords("example.com.")
require.NoError(t, err)

expected := []RR{
{
ID: "210074",
Name: "@",
IdnName: "@",
TTL: "",
Type: "SOA",
Soa: &Soa{
MName: &MName{
Name: "ns3-l2.nic.ru.",
IdnName: "ns3-l2.nic.ru.",
},
RName: &RName{
Name: "dns.nic.ru.",
IdnName: "dns.nic.ru.",
},
Serial: "2011112002",
Refresh: "1440",
Retry: "3600",
Expire: "2592000",
Minimum: "600",
},
},
{
ID: "210075",
Name: "@",
IdnName: "@",
Type: "NS",
Ns: &Ns{
Name: "ns3-l2.nic.ru.",
IdnName: "ns3- l2.nic.ru.",
},
},
{
ID: "210076",
Name: "@",
IdnName: "@",
Type: "NS",
Ns: &Ns{
Name: "ns4-l2.nic.ru.",
IdnName: "ns4-l2.nic.ru.",
},
},
{
ID: "210077",
Name: "@",
IdnName: "@",
Type: "NS",
Ns: &Ns{
Name: "ns8-l2.nic.ru.",
IdnName: "ns8- l2.nic.ru.",
},
},
}

assert.Equal(t, expected, records)
}

func TestClient_GetRecords_error(t *testing.T) {
client := setupTest(t, "/dns-master/services/test/zones/example.com./records",
writeFixtures(http.MethodGet, "errors.xml", http.StatusOK))

_, err := client.GetRecords("example.com.")
require.ErrorIs(t, err, Error{
Text: "Access token expired or not found",
Code: "4097",
})
}

func TestClient_AddTxtRecord(t *testing.T) {
client := setupTest(t, "/dns-master/services/test/zones/example.com./records",
writeFixtures(http.MethodPut, "records_PUT.xml", http.StatusOK))

response, err := client.AddTxtRecord("example.com.", "foo", "txtTXT", 30)
require.NoError(t, err)

expected := &Response{
XMLName: xml.Name{Local: "response"},
Status: "success",
Data: &Data{
Zone: []Zone{
{
Admin: "123/NIC-REG",
HasChanges: "true",
ID: "228095",
IdnName: "test.ru",
Name: "test.ru",
Service: "testservice",
RR: []RR{
{
ID: "210076",
Name: "@",
IdnName: "@",
Type: "NS",
Ns: &Ns{
Name: "ns4-l2.nic.ru.",
IdnName: "ns4-l2.nic.ru.",
},
},
{
ID: "210077",
Name: "@",
IdnName: "@",
Type: "NS",
Ns: &Ns{
Name: "ns8-l2.nic.ru.",
IdnName: "ns8-l2.nic.ru.",
},
},
},
},
},
},
}

assert.Equal(t, expected, response)
}

func TestClient_AddTxtRecord_error(t *testing.T) {
client := setupTest(t, "/dns-master/services/test/zones/example.com./records",
writeFixtures(http.MethodPut, "errors.xml", http.StatusOK))

_, err := client.AddTxtRecord("example.com.", "foo", "txtTXT", 30)
require.ErrorIs(t, err, Error{
Text: "Access token expired or not found",
Code: "4097",
})
}

func TestClient_DeleteRecord_error(t *testing.T) {

client := setupTest(t, "/dns-master/services/test/zones/example.com./records/123",
writeFixtures(http.MethodDelete, "errors.xml", http.StatusUnauthorized))

_, err := client.DeleteRecord("example.com.", "123")
require.ErrorIs(t, err, Error{
Text: "Access token expired or not found",
Code: "4097",
})
}

func TestClient_CommitZone(t *testing.T) {
client := setupTest(t, "/dns-master/services/test/zones/example.com./commit", writeFixtures(http.MethodPost, "commit_POST.xml", http.StatusOK))

response, err := client.CommitZone("example.com.")
require.NoError(t, err)

expected := &Response{
XMLName: xml.Name{Local: "response"},
Status: "success",
}

assert.Equal(t, expected, response)
}

func TestClient_CommitZone_error(t *testing.T) {
client := setupTest(t, "/dns-master/services/test/zones/example.com./commit", writeFixtures(http.MethodPost, "errors.xml", http.StatusOK))

_, err := client.CommitZone("example.com.")
require.ErrorIs(t, err, Error{
Text: "Access token expired or not found",
Code: "4097",
})
}
4 changes: 4 additions & 0 deletions providers/dns/nicru/internal/fixtures/commit_POST.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8" ?>
<response>
<status>success</status>
</response>
7 changes: 7 additions & 0 deletions providers/dns/nicru/internal/fixtures/errors.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8" ?>
<response>
<status>fail</status>
<errors>
<error code="4097">Access token expired or not found</error>
</errors>
</response>
Loading

0 comments on commit 5a203b9

Please sign in to comment.