Skip to content

Commit

Permalink
feat: Domains API operations
Browse files Browse the repository at this point in the history
Added types for Domains. Added a new package for Domains API operations.
Supports List, Create, Update and Delete.
  • Loading branch information
gkats committed Feb 1, 2024
1 parent 82fcf3a commit 1aad70d
Show file tree
Hide file tree
Showing 5 changed files with 305 additions and 1 deletion.
2 changes: 1 addition & 1 deletion clerk.go
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ func (b *defaultBackend) do(req *http.Request, params Queryable, setter Response
func setRequestBody(req *http.Request, params Queryable) error {
// GET requests don't have a body, but we will pass the params
// in the query string.
if req.Method == http.MethodGet {
if req.Method == http.MethodGet && params != nil {
q := req.URL.Query()
params.Add(q)
req.URL.RawQuery = q.Encode()
Expand Down
12 changes: 12 additions & 0 deletions deleted_resource.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package clerk

// DeletedResource describes an API resource that is no longer
// available.
// It's usually encountered as a result of delete API operations.
type DeletedResource struct {
APIResource
ID string `json:"id,omitempty"`
Slug string `json:"slug,omitempty"`
Object string `json:"object"`
Deleted bool `json:"deleted"`
}
25 changes: 25 additions & 0 deletions domain.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package clerk

type Domain struct {
APIResource
ID string `json:"id"`
Object string `json:"object"`
Name string `json:"name"`
IsSatellite bool `json:"is_satellite"`
FrontendAPIURL string `json:"frontend_api_url"`
AccountPortalURL *string `json:"accounts_portal_url,omitempty"`
ProxyURL *string `json:"proxy_url,omitempty"`
CNAMETargets []CNAMETarget `json:"cname_targets,omitempty"`
DevelopmentOrigin string `json:"development_origin"`
}

type CNAMETarget struct {
Host string `json:"host"`
Value string `json:"value"`
}

type DomainList struct {
APIResource
Domains []*Domain `json:"data"`
TotalCount int64 `json:"total_count"`
}
73 changes: 73 additions & 0 deletions domain/domain.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// Package domain provides the Domains API.
package domain

import (
"context"
"net/http"
"net/url"

"github.com/clerk/clerk-sdk-go/v2"
)

const path = "/domains"

type CreateParams struct {
clerk.APIParams
Name *string `json:"name,omitempty"`
ProxyURL *string `json:"proxy_url,omitempty"`
IsSatellite *bool `json:"is_satellite,omitempty"`
}

// Create creates a new domain.
func Create(ctx context.Context, params *CreateParams) (*clerk.Domain, error) {
req := clerk.NewAPIRequest(http.MethodPost, path)
req.SetParams(params)

domain := &clerk.Domain{}
err := clerk.GetBackend().Call(ctx, req, domain)
return domain, err
}

type UpdateParams struct {
clerk.APIParams
Name *string `json:"name,omitempty"`
ProxyURL *string `json:"proxy_url,omitempty"`
}

// Update updates a domain's properties.
func Update(ctx context.Context, id string, params *UpdateParams) (*clerk.Domain, error) {
path, err := url.JoinPath(path, id)
if err != nil {
return nil, err
}
req := clerk.NewAPIRequest(http.MethodPatch, path)
req.SetParams(params)

domain := &clerk.Domain{}
err = clerk.GetBackend().Call(ctx, req, domain)
return domain, err
}

// Delete removes a domain.
func Delete(ctx context.Context, id string) (*clerk.DeletedResource, error) {
path, err := url.JoinPath(path, id)
if err != nil {
return nil, err
}
req := clerk.NewAPIRequest(http.MethodDelete, path)
domain := &clerk.DeletedResource{}
err = clerk.GetBackend().Call(ctx, req, domain)
return domain, err
}

type ListParams struct {
clerk.APIParams
}

// List returns a list of domains
func List(ctx context.Context, params *ListParams) (*clerk.DomainList, error) {
req := clerk.NewAPIRequest(http.MethodGet, path)
list := &clerk.DomainList{}
err := clerk.GetBackend().Call(ctx, req, list)
return list, err
}
194 changes: 194 additions & 0 deletions domain/domain_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
package domain

import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"testing"

"github.com/clerk/clerk-sdk-go/v2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestDomainCreate(t *testing.T) {
name := "clerk.com"
id := "dmn_123"
clerk.SetBackend(clerk.NewBackend(&clerk.BackendConfig{
HTTPClient: &http.Client{
Transport: &mockRoundTripper{
T: t,
in: json.RawMessage(fmt.Sprintf(`{"name":"%s"}`, name)),
out: json.RawMessage(fmt.Sprintf(`{"id":"%s","name":"%s"}`, id, name)),
path: "/v1/domains",
method: http.MethodPost,
},
},
}))

dmn, err := Create(context.Background(), &CreateParams{
Name: clerk.String(name),
})
require.NoError(t, err)
assert.Equal(t, id, dmn.ID)
assert.Equal(t, name, dmn.Name)
}

func TestDomainCreate_Error(t *testing.T) {
clerk.SetBackend(clerk.NewBackend(&clerk.BackendConfig{
HTTPClient: &http.Client{
Transport: &mockRoundTripper{
T: t,
status: http.StatusBadRequest,
out: json.RawMessage(`{
"errors":[{
"code":"create-error-code"
}],
"clerk_trace_id":"create-trace-id"
}`),
},
},
}))

_, err := Create(context.Background(), &CreateParams{})
require.Error(t, err)
apiErr, ok := err.(*clerk.APIErrorResponse)
require.True(t, ok)
assert.Equal(t, "create-trace-id", apiErr.TraceID)
require.Equal(t, 1, len(apiErr.Errors))
assert.Equal(t, "create-error-code", apiErr.Errors[0].Code)
}

func TestDomainUpdate(t *testing.T) {
id := "dmn_456"
name := "clerk.dev"
clerk.SetBackend(clerk.NewBackend(&clerk.BackendConfig{
HTTPClient: &http.Client{
Transport: &mockRoundTripper{
T: t,
in: json.RawMessage(fmt.Sprintf(`{"name":"%s"}`, name)),
out: json.RawMessage(fmt.Sprintf(`{"id":"%s","name":"%s"}`, id, name)),
path: fmt.Sprintf("/v1/domains/%s", id),
method: http.MethodPatch,
},
},
}))

dmn, err := Update(context.Background(), id, &UpdateParams{
Name: clerk.String(name),
})
require.NoError(t, err)
assert.Equal(t, id, dmn.ID)
assert.Equal(t, name, dmn.Name)
}

func TestDomainUpdate_Error(t *testing.T) {
clerk.SetBackend(clerk.NewBackend(&clerk.BackendConfig{
HTTPClient: &http.Client{
Transport: &mockRoundTripper{
T: t,
status: http.StatusBadRequest,
out: json.RawMessage(`{
"errors":[{
"code":"update-error-code"
}],
"clerk_trace_id":"update-trace-id"
}`),
},
},
}))

_, err := Update(context.Background(), "dmn_123", &UpdateParams{})
require.Error(t, err)
apiErr, ok := err.(*clerk.APIErrorResponse)
require.True(t, ok)
assert.Equal(t, "update-trace-id", apiErr.TraceID)
require.Equal(t, 1, len(apiErr.Errors))
assert.Equal(t, "update-error-code", apiErr.Errors[0].Code)
}

func TestDomainDelete(t *testing.T) {
id := "dmn_789"
clerk.SetBackend(clerk.NewBackend(&clerk.BackendConfig{
HTTPClient: &http.Client{
Transport: &mockRoundTripper{
T: t,
out: json.RawMessage(fmt.Sprintf(`{"id":"%s","deleted":true}`, id)),
path: fmt.Sprintf("/v1/domains/%s", id),
method: http.MethodDelete,
},
},
}))

dmn, err := Delete(context.Background(), id)
require.NoError(t, err)
assert.Equal(t, id, dmn.ID)
assert.True(t, dmn.Deleted)
}

func TestDomainList(t *testing.T) {
clerk.SetBackend(clerk.NewBackend(&clerk.BackendConfig{
HTTPClient: &http.Client{
Transport: &mockRoundTripper{
T: t,
out: json.RawMessage(`{
"data": [{"id":"dmn_123","name":"clerk.com"}],
"total_count": 1
}`),
path: "/v1/domains",
method: http.MethodGet,
},
},
}))

list, err := List(context.Background(), &ListParams{})
require.NoError(t, err)
assert.Equal(t, int64(1), list.TotalCount)
assert.Equal(t, 1, len(list.Domains))
assert.Equal(t, "dmn_123", list.Domains[0].ID)
assert.Equal(t, "clerk.com", list.Domains[0].Name)
}

type mockRoundTripper struct {
T *testing.T
// Response status.
status int
// Response body.
out json.RawMessage
// If set, we'll assert that the request body
// matches.
in json.RawMessage
// If set, we'll assert the request path matches.
path string
// If set, we'll assert that the request method matches.
method string
}

func (rt *mockRoundTripper) RoundTrip(r *http.Request) (*http.Response, error) {
if rt.status == 0 {
rt.status = http.StatusOK
}

if rt.method != "" {
require.Equal(rt.T, rt.method, r.Method)
}
if rt.path != "" {
require.Equal(rt.T, rt.path, r.URL.Path)
}
if rt.in != nil {
body, err := io.ReadAll(r.Body)
if err != nil {
return nil, err
}
defer r.Body.Close()
require.JSONEq(rt.T, string(rt.in), string(body))
}

return &http.Response{
StatusCode: rt.status,
Body: io.NopCloser(bytes.NewReader(rt.out)),
}, nil
}

0 comments on commit 1aad70d

Please sign in to comment.