Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for registrant APIs #146

Merged
7 commits merged into from
Aug 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
This conversation was marked as resolved.
Show resolved Hide resolved
} 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
Loading