Skip to content

Commit

Permalink
Add support for registrant APIs (#146)
Browse files Browse the repository at this point in the history
  • Loading branch information
Wilson Lin authored Aug 24, 2023
1 parent 1d14b1b commit e03e247
Show file tree
Hide file tree
Showing 11 changed files with 435 additions and 1 deletion.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

## main

## 1.3.0 (Unreleased)

FEATURES:

- NEW: Added `ListRegistrantChanges`, `CreateRegistrantChange`, `CheckRegistrantChange`, `GetRegistrantChange`, and `DeleteRegistrantChange` APIs to manage registrant changes. (dnsimple/dnsimple-go#146)

## 1.2.1

FEATURES:
Expand Down
10 changes: 9 additions & 1 deletion dnsimple/dnsimple.go
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,15 @@ func (c *Client) request(ctx context.Context, req *http.Request, obj interface{}
if w, ok := obj.(io.Writer); ok {
_, err = io.Copy(w, resp.Body)
} else {
err = json.NewDecoder(resp.Body).Decode(obj)
var raw []byte
raw, err = io.ReadAll(resp.Body)
if err == nil {
if len(raw) == 0 {
// TODO Ignore empty body as temporary workaround for server sending Content-Type: application/json with an empty body.
} else {
err = json.Unmarshal(raw, obj)
}
}
}
}

Expand Down
161 changes: 161 additions & 0 deletions dnsimple/registrar_registrant_changes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
package dnsimple

import (
"context"
"fmt"
)

type CreateRegistrantChangeInput struct {
DomainId string `json:"domain_id"`
ContactId string `json:"contact_id"`
ExtendedAttributes map[string]string `json:"extended_attributes"`
}

type RegistrantChange struct {
Id int `json:"id"`
AccountId int `json:"account_id"`
ContactId int `json:"contact_id"`
DomainId int `json:"domain_id"`
// One of: "new", "pending", "cancelling", "cancelled", "completed".
State string `json:"state"`
ExtendedAttributes map[string]string `json:"extended_attributes"`
RegistryOwnerChange bool `json:"registry_owner_change"`
IrtLockLiftedBy string `json:"irt_lock_lifted_by"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}

type RegistrantChangeResponse struct {
Response
Data *RegistrantChange `json:"data"`
}

type RegistrantChangesListResponse struct {
Response
Data []RegistrantChange `json:"data"`
}

type RegistrantChangeListOptions struct {
// Only include results with a state field exactly matching the given string
State *string `url:"state,omitempty"`
// Only include results with a domain_id field exactly matching the given string
DomainId *string `url:"domain_id,omitempty"`
// Only include results with a contact_id field exactly matching the given string
ContactId *string `url:"contact_id,omitempty"`

ListOptions
}

type CheckRegistrantChangeInput struct {
DomainId string `json:"domain_id"`
ContactId string `json:"contact_id"`
}

type ExtendedAttribute struct {
Name string `json:"name"`
Description string `json:"description"`
Required bool `json:"required"`
Options []ExtendedAttributeOption `json:"options"`
}

type ExtendedAttributeOption struct {
Title string `json:"title"`
Value string `json:"value"`
Description string `json:"description"`
}

type RegistrantChangeCheck struct {
DomainId int `json:"domain_id"`
ContactId int `json:"contact_id"`
ExtendedAttributes []ExtendedAttribute `json:"extended_attributes"`
RegistryOwnerChange bool `json:"registry_owner_change"`
}

type RegistrantChangeCheckResponse struct {
Response
Data *RegistrantChangeCheck `json:"data"`
}

type RegistrantChangeDeleteResponse struct {
Response
}

// ListRegistrantChange lists registrant changes in the account.
//
// See https://developer.dnsimple.com/v2/registrar/#listRegistrantChanges
func (s *RegistrarService) ListRegistrantChange(ctx context.Context, accountID string, options *RegistrantChangeListOptions) (*RegistrantChangesListResponse, error) {
path := versioned(fmt.Sprintf("/%v/registrar/registrant_changes", accountID))
changeResponse := &RegistrantChangesListResponse{}

resp, err := s.client.get(ctx, path, changeResponse)
if err != nil {
return nil, err
}

changeResponse.HTTPResponse = resp
return changeResponse, nil
}

// CreateRegistrantChange starts a registrant change.
//
// See https://developer.dnsimple.com/v2/registrar/#createRegistrantChange
func (s *RegistrarService) CreateRegistrantChange(ctx context.Context, accountID string, input *CreateRegistrantChangeInput) (*RegistrantChangeResponse, error) {
path := versioned(fmt.Sprintf("/%v/registrar/registrant_changes", accountID))
changeResponse := &RegistrantChangeResponse{}

resp, err := s.client.post(ctx, path, input, changeResponse)
if err != nil {
return nil, err
}

changeResponse.HTTPResponse = resp
return changeResponse, nil
}

// CheckRegistrantChange retrieves the requirements of a registrant change.
//
// See https://developer.dnsimple.com/v2/registrar/#checkRegistrantChange
func (s *RegistrarService) CheckRegistrantChange(ctx context.Context, accountID string, input *CheckRegistrantChangeInput) (*RegistrantChangeCheckResponse, error) {
path := versioned(fmt.Sprintf("/%v/registrar/registrant_changes/check", accountID))
checkResponse := &RegistrantChangeCheckResponse{}

resp, err := s.client.post(ctx, path, input, checkResponse)
if err != nil {
return nil, err
}

checkResponse.HTTPResponse = resp
return checkResponse, nil
}

// GetRegistrantChange retrieves the details of an existing registrant change.
//
// See https://developer.dnsimple.com/v2/registrar/#getRegistrantChange
func (s *RegistrarService) GetRegistrantChange(ctx context.Context, accountID string, registrantChange int) (*RegistrantChangeResponse, error) {
path := versioned(fmt.Sprintf("/%v/registrar/registrant_changes/%v", accountID, registrantChange))
checkResponse := &RegistrantChangeResponse{}

resp, err := s.client.get(ctx, path, checkResponse)
if err != nil {
return nil, err
}

checkResponse.HTTPResponse = resp
return checkResponse, nil
}

// DeleteRegistrantChange cancels an ongoing registrant change from the account.
//
// See https://developer.dnsimple.com/v2/registrar/#deleteRegistrantChange
func (s *RegistrarService) DeleteRegistrantChange(ctx context.Context, accountID string, registrantChange int) (*RegistrantChangeDeleteResponse, error) {
path := versioned(fmt.Sprintf("/%v/registrar/registrant_changes/%v", accountID, registrantChange))
deleteResponse := &RegistrantChangeDeleteResponse{}

resp, err := s.client.delete(ctx, path, nil, deleteResponse)
if err != nil {
return nil, err
}

deleteResponse.HTTPResponse = resp
return deleteResponse, nil
}
158 changes: 158 additions & 0 deletions dnsimple/registrar_registrant_changes_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
package dnsimple

import (
"context"
"io"
"net/http"
"testing"

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

func TestRegistrarService_ListRegistrantChanges(t *testing.T) {
setupMockServer()
defer teardownMockServer()

mux.HandleFunc("/v2/1010/registrar/registrant_changes", func(w http.ResponseWriter, r *http.Request) {
httpResponse := httpResponseFixture(t, "/api/listRegistrantChanges/success.http")

testMethod(t, r, "GET")
testHeaders(t, r)

w.WriteHeader(httpResponse.StatusCode)
_, _ = io.Copy(w, httpResponse.Body)
})

res, err := client.Registrar.ListRegistrantChange(context.Background(), "1010", &RegistrantChangeListOptions{})

assert.NoError(t, err)
changes := res.Data
assert.Equal(t, changes[0], RegistrantChange{
Id: 101,
AccountId: 101,
DomainId: 101,
ContactId: 101,
State: "new",
ExtendedAttributes: map[string]string{},
RegistryOwnerChange: true,
IrtLockLiftedBy: "",
CreatedAt: "2017-02-03T17:43:22Z",
UpdatedAt: "2017-02-03T17:43:22Z",
})
}

func TestRegistrarService_CreateRegistrantChange(t *testing.T) {
setupMockServer()
defer teardownMockServer()

mux.HandleFunc("/v2/1010/registrar/registrant_changes", func(w http.ResponseWriter, r *http.Request) {
httpResponse := httpResponseFixture(t, "/api/createRegistrantChange/success.http")

testMethod(t, r, "POST")
testHeaders(t, r)

w.WriteHeader(httpResponse.StatusCode)
_, _ = io.Copy(w, httpResponse.Body)
})

res, err := client.Registrar.CreateRegistrantChange(context.Background(), "1010", &CreateRegistrantChangeInput{
DomainId: "example.com",
ContactId: "101",
ExtendedAttributes: map[string]string{},
})

assert.NoError(t, err)
change := res.Data
assert.Equal(t, change, &RegistrantChange{
Id: 101,
AccountId: 101,
DomainId: 101,
ContactId: 101,
State: "new",
ExtendedAttributes: map[string]string{},
RegistryOwnerChange: true,
IrtLockLiftedBy: "",
CreatedAt: "2017-02-03T17:43:22Z",
UpdatedAt: "2017-02-03T17:43:22Z",
})
}

func TestRegistrarService_CheckRegistrantChange(t *testing.T) {
setupMockServer()
defer teardownMockServer()

mux.HandleFunc("/v2/1010/registrar/registrant_changes/check", func(w http.ResponseWriter, r *http.Request) {
httpResponse := httpResponseFixture(t, "/api/checkRegistrantChange/success.http")

testMethod(t, r, "POST")
testHeaders(t, r)

w.WriteHeader(httpResponse.StatusCode)
_, _ = io.Copy(w, httpResponse.Body)
})

res, err := client.Registrar.CheckRegistrantChange(context.Background(), "1010", &CheckRegistrantChangeInput{
DomainId: "example.com",
ContactId: "101",
})

assert.NoError(t, err)
change := res.Data
assert.Equal(t, change, &RegistrantChangeCheck{
DomainId: 101,
ContactId: 101,
ExtendedAttributes: make([]ExtendedAttribute, 0),
RegistryOwnerChange: true,
})
}

func TestRegistrarService_GetRegistrantChange(t *testing.T) {
setupMockServer()
defer teardownMockServer()

mux.HandleFunc("/v2/1010/registrar/registrant_changes/101", func(w http.ResponseWriter, r *http.Request) {
httpResponse := httpResponseFixture(t, "/api/getRegistrantChange/success.http")

testMethod(t, r, "GET")
testHeaders(t, r)

w.WriteHeader(httpResponse.StatusCode)
_, _ = io.Copy(w, httpResponse.Body)
})

res, err := client.Registrar.GetRegistrantChange(context.Background(), "1010", 101)

assert.NoError(t, err)
change := res.Data
assert.Equal(t, change, &RegistrantChange{
Id: 101,
AccountId: 101,
DomainId: 101,
ContactId: 101,
State: "new",
ExtendedAttributes: map[string]string{},
RegistryOwnerChange: true,
IrtLockLiftedBy: "",
CreatedAt: "2017-02-03T17:43:22Z",
UpdatedAt: "2017-02-03T17:43:22Z",
})
}

func TestRegistrarService_DeleteRegistrantChange(t *testing.T) {
setupMockServer()
defer teardownMockServer()

mux.HandleFunc("/v2/1010/registrar/registrant_changes/101", func(w http.ResponseWriter, r *http.Request) {
httpResponse := httpResponseFixture(t, "/api/deleteRegistrantChange/success.http")

testMethod(t, r, "DELETE")
testHeaders(t, r)

w.WriteHeader(httpResponse.StatusCode)
_, _ = io.Copy(w, httpResponse.Body)
})

_, err := client.Registrar.DeleteRegistrantChange(context.Background(), "1010", 101)

assert.NoError(t, err)
}
14 changes: 14 additions & 0 deletions fixtures.http/api/checkRegistrantChange/error-contactnotfound.http
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
HTTP/1.1 404
server: nginx
date: Tue, 22 Aug 2023 13:59:02 GMT
content-type: application/json; charset=utf-8
x-ratelimit-limit: 2400
x-ratelimit-remaining: 2398
x-ratelimit-reset: 1692716201
x-work-with-us: Love automation? So do we! https://dnsimple.com/jobs
cache-control: no-cache
x-request-id: b1dd3f42-ebb9-42fd-a121-d595de96f667
x-runtime: 0.019122
strict-transport-security: max-age=63072000

{"message":"Contact `21` not found"}
15 changes: 15 additions & 0 deletions fixtures.http/api/checkRegistrantChange/error-domainnotfound.http
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
HTTP/1.1 404
server: nginx
date: Tue, 22 Aug 2023 11:09:40 GMT
content-type: application/json; charset=utf-8
x-ratelimit-limit: 2400
x-ratelimit-remaining: 2395
x-ratelimit-reset: 1692705338
x-work-with-us: Love automation? So do we! https://dnsimple.com/jobs
etag: W/"cef1e7d85d0b9bfd25e81b812891d34f"
cache-control: max-age=0, private, must-revalidate
x-request-id: 5b0d8bfb-7b6a-40b5-a079-b640fd817e34
x-runtime: 3.066249
strict-transport-security: max-age=63072000

{"message":"Domain `dnsimple-rraform.bio` not found"}
Loading

0 comments on commit e03e247

Please sign in to comment.