Skip to content

Commit 5a203b9

Browse files
committed
review: 5
1 parent caf1120 commit 5a203b9

File tree

8 files changed

+528
-130
lines changed

8 files changed

+528
-130
lines changed

providers/dns/nicru/internal/client.go

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"encoding/xml"
77
"errors"
88
"fmt"
9+
"io"
910
"net/http"
1011
"net/url"
1112
"strconv"
@@ -20,6 +21,19 @@ const tokenURL = defaultBaseURL + "/oauth/token"
2021

2122
const successStatus = "success"
2223

24+
// Trimmer trim all XML fields.
25+
type Trimmer struct {
26+
decoder *xml.Decoder
27+
}
28+
29+
func (tr Trimmer) Token() (xml.Token, error) {
30+
t, err := tr.decoder.Token()
31+
if cd, ok := t.(xml.CharData); ok {
32+
t = xml.CharData(bytes.TrimSpace(cd))
33+
}
34+
return t, err
35+
}
36+
2337
// OauthConfiguration credentials.
2438
type OauthConfiguration struct {
2539
OAuth2ClientID string
@@ -77,7 +91,7 @@ func NewClient(httpClient *http.Client, serviceName string) (*Client, error) {
7791
}, nil
7892
}
7993

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

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

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

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

109-
var records []*RR
123+
var records []RR
110124
for _, zone := range apiResponse.Data.Zone {
111125
records = append(records, zone.RR...)
112126
}
@@ -115,7 +129,7 @@ func (c *Client) GetRecords(fqdn string) ([]*RR, error) {
115129
}
116130

117131
func (c *Client) AddTxtRecord(zoneName string, name string, content string, ttl int) (*Response, error) {
118-
request := &Request{RRList: &RrList{RR: []*RR{{
132+
request := &Request{RRList: &RrList{RR: []RR{{
119133
Name: name,
120134
TTL: strconv.Itoa(ttl),
121135
Type: "TXT",
@@ -155,6 +169,9 @@ func (c *Client) addRecords(zoneName string, request *Request) (*Response, error
155169
return nil, err
156170
}
157171

172+
// PUT https://api.nic.ru/dns-master/services/<service_id>/zones/<zone_name>/records
173+
// PUT https://api.nic.ru/dns-master/services/TESTSERVICE/zones/TEST.RU/records
174+
158175
req, err := http.NewRequest(http.MethodPut, endpoint.String(), body)
159176
if err != nil {
160177
return nil, err
@@ -168,15 +185,23 @@ func (c *Client) do(req *http.Request) (*Response, error) {
168185
if err != nil {
169186
return nil, err
170187
}
188+
171189
defer func() { _ = resp.Body.Close() }()
172190

173191
apiResponse := &Response{}
174192

175-
err = xml.NewDecoder(resp.Body).Decode(apiResponse)
193+
raw, err := io.ReadAll(resp.Body)
176194
if err != nil {
177195
return nil, err
178196
}
179197

198+
decoder := xml.NewTokenDecoder(Trimmer{decoder: xml.NewDecoder(bytes.NewReader(raw))})
199+
200+
err = decoder.Decode(apiResponse)
201+
if err != nil {
202+
return nil, fmt.Errorf("[status code=%d] %s", resp.StatusCode, string(raw))
203+
}
204+
180205
if apiResponse.Status != successStatus {
181206
return nil, apiResponse.Errors.Error
182207
}
Lines changed: 286 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,286 @@
1+
package internal
2+
3+
import (
4+
"encoding/xml"
5+
"fmt"
6+
"io"
7+
"net/http"
8+
"net/http/httptest"
9+
"net/url"
10+
"os"
11+
"path/filepath"
12+
"testing"
13+
14+
"github.com/stretchr/testify/assert"
15+
"github.com/stretchr/testify/require"
16+
)
17+
18+
func setupTest(t *testing.T, pattern string, handler http.HandlerFunc) *Client {
19+
t.Helper()
20+
21+
mux := http.NewServeMux()
22+
server := httptest.NewServer(mux)
23+
t.Cleanup(server.Close)
24+
25+
mux.HandleFunc(pattern, handler)
26+
27+
client, err := NewClient(server.Client(), "test")
28+
require.NoError(t, err)
29+
30+
client.baseURL, _ = url.Parse(server.URL)
31+
32+
return client
33+
}
34+
35+
func writeFixtures(method string, filename string, status int) http.HandlerFunc {
36+
return func(rw http.ResponseWriter, req *http.Request) {
37+
if req.Method != method {
38+
http.Error(rw, fmt.Sprintf("unsupported method: %s", req.Method), http.StatusMethodNotAllowed)
39+
return
40+
}
41+
42+
file, err := os.Open(filepath.Join("fixtures", filename))
43+
if err != nil {
44+
http.Error(rw, err.Error(), http.StatusInternalServerError)
45+
return
46+
}
47+
48+
defer func() { _ = file.Close() }()
49+
50+
rw.WriteHeader(status)
51+
_, err = io.Copy(rw, file)
52+
if err != nil {
53+
http.Error(rw, err.Error(), http.StatusInternalServerError)
54+
return
55+
}
56+
}
57+
}
58+
59+
func TestClient_GetZones(t *testing.T) {
60+
client := setupTest(t, "/dns-master/services/test/zones",
61+
writeFixtures(http.MethodGet, "zones_GET.xml", http.StatusOK))
62+
63+
zones, err := client.GetZones()
64+
require.NoError(t, err)
65+
66+
expected := []Zone{
67+
{
68+
Admin: "123/NIC-REG",
69+
Enable: "true",
70+
HasChanges: "false",
71+
HasPrimary: "true",
72+
ID: "227645",
73+
IdnName: "тест.рф",
74+
Name: "xn—e1aybc.xn--p1ai",
75+
Payer: "123/NIC-REG",
76+
Service: "myservice",
77+
},
78+
{
79+
Admin: "123/NIC-REG",
80+
Enable: "true",
81+
HasChanges: "false",
82+
HasPrimary: "true",
83+
ID: "227642",
84+
IdnName: "example.ru",
85+
Name: "example.ru",
86+
Payer: "123/NIC-REG",
87+
Service: "myservice",
88+
},
89+
{
90+
Admin: "123/NIC-REG",
91+
Enable: "true",
92+
HasChanges: "false",
93+
HasPrimary: "true",
94+
ID: "227643",
95+
IdnName: "test.su",
96+
Name: "test.su",
97+
Payer: "123/NIC-REG",
98+
Service: "myservice",
99+
},
100+
}
101+
102+
assert.Equal(t, expected, zones)
103+
}
104+
105+
func TestClient_GetZones_error(t *testing.T) {
106+
client := setupTest(t, "/dns-master/services/test/zones",
107+
writeFixtures(http.MethodGet, "errors.xml", http.StatusOK))
108+
109+
_, err := client.GetZones()
110+
require.ErrorIs(t, err, Error{
111+
Text: "Access token expired or not found",
112+
Code: "4097",
113+
})
114+
}
115+
116+
func TestClient_GetRecords(t *testing.T) {
117+
client := setupTest(t, "/dns-master/services/test/zones/example.com./records",
118+
writeFixtures(http.MethodGet, "records_GET.xml", http.StatusOK))
119+
120+
records, err := client.GetRecords("example.com.")
121+
require.NoError(t, err)
122+
123+
expected := []RR{
124+
{
125+
ID: "210074",
126+
Name: "@",
127+
IdnName: "@",
128+
TTL: "",
129+
Type: "SOA",
130+
Soa: &Soa{
131+
MName: &MName{
132+
Name: "ns3-l2.nic.ru.",
133+
IdnName: "ns3-l2.nic.ru.",
134+
},
135+
RName: &RName{
136+
Name: "dns.nic.ru.",
137+
IdnName: "dns.nic.ru.",
138+
},
139+
Serial: "2011112002",
140+
Refresh: "1440",
141+
Retry: "3600",
142+
Expire: "2592000",
143+
Minimum: "600",
144+
},
145+
},
146+
{
147+
ID: "210075",
148+
Name: "@",
149+
IdnName: "@",
150+
Type: "NS",
151+
Ns: &Ns{
152+
Name: "ns3-l2.nic.ru.",
153+
IdnName: "ns3- l2.nic.ru.",
154+
},
155+
},
156+
{
157+
ID: "210076",
158+
Name: "@",
159+
IdnName: "@",
160+
Type: "NS",
161+
Ns: &Ns{
162+
Name: "ns4-l2.nic.ru.",
163+
IdnName: "ns4-l2.nic.ru.",
164+
},
165+
},
166+
{
167+
ID: "210077",
168+
Name: "@",
169+
IdnName: "@",
170+
Type: "NS",
171+
Ns: &Ns{
172+
Name: "ns8-l2.nic.ru.",
173+
IdnName: "ns8- l2.nic.ru.",
174+
},
175+
},
176+
}
177+
178+
assert.Equal(t, expected, records)
179+
}
180+
181+
func TestClient_GetRecords_error(t *testing.T) {
182+
client := setupTest(t, "/dns-master/services/test/zones/example.com./records",
183+
writeFixtures(http.MethodGet, "errors.xml", http.StatusOK))
184+
185+
_, err := client.GetRecords("example.com.")
186+
require.ErrorIs(t, err, Error{
187+
Text: "Access token expired or not found",
188+
Code: "4097",
189+
})
190+
}
191+
192+
func TestClient_AddTxtRecord(t *testing.T) {
193+
client := setupTest(t, "/dns-master/services/test/zones/example.com./records",
194+
writeFixtures(http.MethodPut, "records_PUT.xml", http.StatusOK))
195+
196+
response, err := client.AddTxtRecord("example.com.", "foo", "txtTXT", 30)
197+
require.NoError(t, err)
198+
199+
expected := &Response{
200+
XMLName: xml.Name{Local: "response"},
201+
Status: "success",
202+
Data: &Data{
203+
Zone: []Zone{
204+
{
205+
Admin: "123/NIC-REG",
206+
HasChanges: "true",
207+
ID: "228095",
208+
IdnName: "test.ru",
209+
Name: "test.ru",
210+
Service: "testservice",
211+
RR: []RR{
212+
{
213+
ID: "210076",
214+
Name: "@",
215+
IdnName: "@",
216+
Type: "NS",
217+
Ns: &Ns{
218+
Name: "ns4-l2.nic.ru.",
219+
IdnName: "ns4-l2.nic.ru.",
220+
},
221+
},
222+
{
223+
ID: "210077",
224+
Name: "@",
225+
IdnName: "@",
226+
Type: "NS",
227+
Ns: &Ns{
228+
Name: "ns8-l2.nic.ru.",
229+
IdnName: "ns8-l2.nic.ru.",
230+
},
231+
},
232+
},
233+
},
234+
},
235+
},
236+
}
237+
238+
assert.Equal(t, expected, response)
239+
}
240+
241+
func TestClient_AddTxtRecord_error(t *testing.T) {
242+
client := setupTest(t, "/dns-master/services/test/zones/example.com./records",
243+
writeFixtures(http.MethodPut, "errors.xml", http.StatusOK))
244+
245+
_, err := client.AddTxtRecord("example.com.", "foo", "txtTXT", 30)
246+
require.ErrorIs(t, err, Error{
247+
Text: "Access token expired or not found",
248+
Code: "4097",
249+
})
250+
}
251+
252+
func TestClient_DeleteRecord_error(t *testing.T) {
253+
254+
client := setupTest(t, "/dns-master/services/test/zones/example.com./records/123",
255+
writeFixtures(http.MethodDelete, "errors.xml", http.StatusUnauthorized))
256+
257+
_, err := client.DeleteRecord("example.com.", "123")
258+
require.ErrorIs(t, err, Error{
259+
Text: "Access token expired or not found",
260+
Code: "4097",
261+
})
262+
}
263+
264+
func TestClient_CommitZone(t *testing.T) {
265+
client := setupTest(t, "/dns-master/services/test/zones/example.com./commit", writeFixtures(http.MethodPost, "commit_POST.xml", http.StatusOK))
266+
267+
response, err := client.CommitZone("example.com.")
268+
require.NoError(t, err)
269+
270+
expected := &Response{
271+
XMLName: xml.Name{Local: "response"},
272+
Status: "success",
273+
}
274+
275+
assert.Equal(t, expected, response)
276+
}
277+
278+
func TestClient_CommitZone_error(t *testing.T) {
279+
client := setupTest(t, "/dns-master/services/test/zones/example.com./commit", writeFixtures(http.MethodPost, "errors.xml", http.StatusOK))
280+
281+
_, err := client.CommitZone("example.com.")
282+
require.ErrorIs(t, err, Error{
283+
Text: "Access token expired or not found",
284+
Code: "4097",
285+
})
286+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<?xml version="1.0" encoding="UTF-8" ?>
2+
<response>
3+
<status>success</status>
4+
</response>
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?xml version="1.0" encoding="UTF-8" ?>
2+
<response>
3+
<status>fail</status>
4+
<errors>
5+
<error code="4097">Access token expired or not found</error>
6+
</errors>
7+
</response>

0 commit comments

Comments
 (0)