From 2efe0cfad3ef64ef53414490131edf2a5f2addb0 Mon Sep 17 00:00:00 2001 From: Ye Chen Date: Tue, 19 Nov 2024 10:49:23 -0500 Subject: [PATCH] update domains --- domains.go | 23 ++ test/integration/domains_test.go | 29 ++ .../fixtures/TestDomain_Clone.yaml | 267 ++++++++++++++++++ test/unit/domain_test.go | 54 ++++ test/unit/fixtures/domain_clone.json | 18 ++ test/unit/fixtures/domain_import.json | 18 ++ test/unit/util_test.go | 15 + 7 files changed, 424 insertions(+) create mode 100644 test/integration/fixtures/TestDomain_Clone.yaml create mode 100644 test/unit/domain_test.go create mode 100644 test/unit/fixtures/domain_clone.json create mode 100644 test/unit/fixtures/domain_import.json diff --git a/domains.go b/domains.go index 0bc05bdd2..0bc65aa18 100644 --- a/domains.go +++ b/domains.go @@ -165,6 +165,15 @@ const ( DomainStatusHasErrors DomainStatus = "has_errors" ) +type DomainCloneOptions struct { + Domain string `json:"domain"` +} + +type DomainImportOptions struct { + Domain string `json:"domain"` + RemoteNameserver string `json:"remove_nameserver"` +} + // GetUpdateOptions converts a Domain to DomainUpdateOptions for use in UpdateDomain func (d Domain) GetUpdateOptions() (du DomainUpdateOptions) { du.Domain = d.Domain @@ -244,3 +253,17 @@ func (c *Client) GetDomainZoneFile(ctx context.Context, domainID int) (*DomainZo return response, nil } + +// CloneDomain clones a Domain and all associated DNS records from a Domain that is registered in Linode's DNS manager. +func (c *Client) CloneDomain(ctx context.Context, domainID int, opts DomainCloneOptions) (*Domain, error) { + e := formatAPIPath("domains/%d/clone", domainID) + + return doPOSTRequest[Domain](ctx, c, e, opts) +} + +// ImportDomain imports a domain zone from a remote nameserver. +func (c *Client) ImportDomain(ctx context.Context, opts DomainImportOptions) (*Domain, error) { + e := "domains/import" + + return doPOSTRequest[Domain](ctx, c, e, opts) +} diff --git a/test/integration/domains_test.go b/test/integration/domains_test.go index 54da98e20..61d866ba5 100644 --- a/test/integration/domains_test.go +++ b/test/integration/domains_test.go @@ -107,3 +107,32 @@ func setupDomain(t *testing.T, fixturesYaml string) (*linodego.Client, *linodego } return client, domain, teardown, err } + +func TestDomain_Clone_smoke(t *testing.T) { + client, domainToClone, teardown, err := setupDomain(t, "fixtures/TestDomain_Clone") + + if err != nil { + t.Errorf("Error creating domain: %v", err) + } + + domain, err := client.CloneDomain(context.Background(), domainToClone.ID, linodego.DomainCloneOptions{ + Domain: "linodego-domain-clone.com", + }) + if err != nil { + t.Errorf("Error cloning domain, expected struct, got error %v", err) + } + + cloneTeardown := func() { + if err := client.DeleteDomain(context.Background(), domain.ID); err != nil { + t.Errorf("Expected to delete a domain, but got %v", err) + } + teardown() + } + + defer cloneTeardown() + + // when comparing fixtures to random value Domain will differ + if domain.SOAEmail != domainToClone.SOAEmail { + t.Errorf("Domain returned does not match domain clone request") + } +} diff --git a/test/integration/fixtures/TestDomain_Clone.yaml b/test/integration/fixtures/TestDomain_Clone.yaml new file mode 100644 index 000000000..42cb8a1d1 --- /dev/null +++ b/test/integration/fixtures/TestDomain_Clone.yaml @@ -0,0 +1,267 @@ +--- +version: 1 +interactions: +- request: + body: '{"domain":"linodego-blue-test.com","type":"master","soa_email":"example@example.com","master_ips":null,"axfr_ips":null,"tags":null}' + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - linodego/dev https://github.com/linode/linodego + url: https://api.linode.com/v4beta/domains + method: POST + response: + body: '{"id": 3163304, "type": "master", "domain": "linodego-blue-test.com", "tags": + [], "group": "", "status": "active", "errors": "", "description": "", "soa_email": + "example@example.com", "retry_sec": 0, "master_ips": [], "axfr_ips": [], "expire_sec": + 0, "refresh_sec": 0, "ttl_sec": 0, "created": "2018-01-02T03:04:05", "updated": + "2018-01-02T03:04:05"}' + headers: + Access-Control-Allow-Credentials: + - "true" + Access-Control-Allow-Headers: + - Authorization, Origin, X-Requested-With, Content-Type, Accept, X-Filter + Access-Control-Allow-Methods: + - HEAD, GET, OPTIONS, POST, PUT, DELETE + Access-Control-Allow-Origin: + - '*' + Access-Control-Expose-Headers: + - X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Status + Akamai-Internal-Account: + - '*' + Cache-Control: + - max-age=0, no-cache, no-store + Connection: + - keep-alive + Content-Length: + - "350" + Content-Security-Policy: + - default-src 'none' + Content-Type: + - application/json + Expires: + - Tue, 19 Nov 2024 15:25:43 GMT + Pragma: + - no-cache + Strict-Transport-Security: + - max-age=31536000 + Vary: + - Authorization, X-Filter + X-Accepted-Oauth-Scopes: + - domains:read_write + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + - DENY + X-Oauth-Scopes: + - account:read_write databases:read_write domains:read_write events:read_write + firewall:read_write images:read_write ips:read_write linodes:read_write lke:read_write + longview:read_write nodebalancers:read_write object_storage:read_write stackscripts:read_write + volumes:read_write vpc:read_write + X-Ratelimit-Limit: + - "1600" + X-Xss-Protection: + - 1; mode=block + status: 200 OK + code: 200 + duration: "" +- request: + body: '{"domain":"linodego-domain-clone.com"}' + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - linodego/dev https://github.com/linode/linodego + url: https://api.linode.com/v4beta/domains/3163304/clone + method: POST + response: + body: '{"id": 3163305, "type": "master", "domain": "linodego-domain-clone.com", + "tags": [], "group": "", "status": "active", "errors": "", "description": "", + "soa_email": "example@example.com", "retry_sec": 0, "master_ips": [], "axfr_ips": + [], "expire_sec": 0, "refresh_sec": 0, "ttl_sec": 0, "created": "2018-01-02T03:04:05", + "updated": "2018-01-02T03:04:05"}' + headers: + Access-Control-Allow-Credentials: + - "true" + Access-Control-Allow-Headers: + - Authorization, Origin, X-Requested-With, Content-Type, Accept, X-Filter + Access-Control-Allow-Methods: + - HEAD, GET, OPTIONS, POST, PUT, DELETE + Access-Control-Allow-Origin: + - '*' + Access-Control-Expose-Headers: + - X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Status + Akamai-Internal-Account: + - '*' + Cache-Control: + - max-age=0, no-cache, no-store + Connection: + - keep-alive + Content-Length: + - "353" + Content-Security-Policy: + - default-src 'none' + Content-Type: + - application/json + Expires: + - Tue, 19 Nov 2024 15:25:45 GMT + Pragma: + - no-cache + Strict-Transport-Security: + - max-age=31536000 + Vary: + - Authorization, X-Filter + X-Accepted-Oauth-Scopes: + - domains:read_write + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + - DENY + X-Oauth-Scopes: + - account:read_write databases:read_write domains:read_write events:read_write + firewall:read_write images:read_write ips:read_write linodes:read_write lke:read_write + longview:read_write nodebalancers:read_write object_storage:read_write stackscripts:read_write + volumes:read_write vpc:read_write + X-Ratelimit-Limit: + - "1600" + X-Xss-Protection: + - 1; mode=block + status: 200 OK + code: 200 + duration: "" +- request: + body: "" + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - linodego/dev https://github.com/linode/linodego + url: https://api.linode.com/v4beta/domains/3163305 + method: DELETE + response: + body: '{}' + headers: + Access-Control-Allow-Credentials: + - "true" + Access-Control-Allow-Headers: + - Authorization, Origin, X-Requested-With, Content-Type, Accept, X-Filter + Access-Control-Allow-Methods: + - HEAD, GET, OPTIONS, POST, PUT, DELETE + Access-Control-Allow-Origin: + - '*' + Access-Control-Expose-Headers: + - X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Status + Akamai-Internal-Account: + - '*' + Cache-Control: + - max-age=0, no-cache, no-store + Connection: + - keep-alive + Content-Length: + - "2" + Content-Security-Policy: + - default-src 'none' + Content-Type: + - application/json + Expires: + - Tue, 19 Nov 2024 15:25:45 GMT + Pragma: + - no-cache + Strict-Transport-Security: + - max-age=31536000 + Vary: + - Authorization, X-Filter + X-Accepted-Oauth-Scopes: + - domains:read_write + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + - DENY + X-Oauth-Scopes: + - account:read_write databases:read_write domains:read_write events:read_write + firewall:read_write images:read_write ips:read_write linodes:read_write lke:read_write + longview:read_write nodebalancers:read_write object_storage:read_write stackscripts:read_write + volumes:read_write vpc:read_write + X-Ratelimit-Limit: + - "1600" + X-Xss-Protection: + - 1; mode=block + status: 200 OK + code: 200 + duration: "" +- request: + body: "" + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - linodego/dev https://github.com/linode/linodego + url: https://api.linode.com/v4beta/domains/3163304 + method: DELETE + response: + body: '{}' + headers: + Access-Control-Allow-Credentials: + - "true" + Access-Control-Allow-Headers: + - Authorization, Origin, X-Requested-With, Content-Type, Accept, X-Filter + Access-Control-Allow-Methods: + - HEAD, GET, OPTIONS, POST, PUT, DELETE + Access-Control-Allow-Origin: + - '*' + Access-Control-Expose-Headers: + - X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Status + Akamai-Internal-Account: + - '*' + Cache-Control: + - max-age=0, no-cache, no-store + Connection: + - keep-alive + Content-Length: + - "2" + Content-Security-Policy: + - default-src 'none' + Content-Type: + - application/json + Expires: + - Tue, 19 Nov 2024 15:25:45 GMT + Pragma: + - no-cache + Strict-Transport-Security: + - max-age=31536000 + Vary: + - Authorization, X-Filter + X-Accepted-Oauth-Scopes: + - domains:read_write + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + - DENY + X-Oauth-Scopes: + - account:read_write databases:read_write domains:read_write events:read_write + firewall:read_write images:read_write ips:read_write linodes:read_write lke:read_write + longview:read_write nodebalancers:read_write object_storage:read_write stackscripts:read_write + volumes:read_write vpc:read_write + X-Ratelimit-Limit: + - "1600" + X-Xss-Protection: + - 1; mode=block + status: 200 OK + code: 200 + duration: "" diff --git a/test/unit/domain_test.go b/test/unit/domain_test.go new file mode 100644 index 000000000..0bae05a34 --- /dev/null +++ b/test/unit/domain_test.go @@ -0,0 +1,54 @@ +package unit + +import ( + "context" + "github.com/linode/linodego" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestDomain_Clone(t *testing.T) { + fixtureData, err := fixtures.GetFixture("domain_clone") + assert.NoError(t, err) + + var base ClientBaseCase + base.SetUp(t) + defer base.TearDown(t) + + requestData := linodego.DomainCloneOptions{ + Domain: "linodego-domain-clone.com", + } + + domainToCloneID := 123 + base.MockPost(formatMockAPIPath("domains/%d/clone", 123), fixtureData) + + domain, err := base.Client.CloneDomain(context.Background(), domainToCloneID, requestData) + + assert.NoError(t, err) + + assert.Equal(t, "linodego-domain-clone.com", domain.Domain) + assert.Equal(t, "admin@example.org", domain.SOAEmail) +} + +func TestDomain_Import(t *testing.T) { + fixtureData, err := fixtures.GetFixture("domain_import") + assert.NoError(t, err) + + var base ClientBaseCase + base.SetUp(t) + defer base.TearDown(t) + + requestData := linodego.DomainImportOptions{ + Domain: "linodego-domain-import.com", + RemoteNameserver: "linodego-domain-import-nameserver.com", + } + + base.MockPost("domains/import", fixtureData) + + domain, err := base.Client.ImportDomain(context.Background(), requestData) + + assert.NoError(t, err) + + assert.Equal(t, "example.org", domain.Domain) + assert.Equal(t, "admin@example.org", domain.SOAEmail) +} diff --git a/test/unit/fixtures/domain_clone.json b/test/unit/fixtures/domain_clone.json new file mode 100644 index 000000000..2aee5078d --- /dev/null +++ b/test/unit/fixtures/domain_clone.json @@ -0,0 +1,18 @@ +{ + "axfr_ips": [], + "description": null, + "domain": "linodego-domain-clone.com", + "expire_sec": 300, + "id": 1234, + "master_ips": [], + "refresh_sec": 300, + "retry_sec": 300, + "soa_email": "admin@example.org", + "status": "active", + "tags": [ + "example tag", + "another example" + ], + "ttl_sec": 300, + "type": "master" +} \ No newline at end of file diff --git a/test/unit/fixtures/domain_import.json b/test/unit/fixtures/domain_import.json new file mode 100644 index 000000000..81848fbbc --- /dev/null +++ b/test/unit/fixtures/domain_import.json @@ -0,0 +1,18 @@ +{ + "axfr_ips": [], + "description": null, + "domain": "example.org", + "expire_sec": 300, + "id": 1234, + "master_ips": [], + "refresh_sec": 300, + "retry_sec": 300, + "soa_email": "admin@example.org", + "status": "active", + "tags": [ + "example tag", + "another example" + ], + "ttl_sec": 300, + "type": "master" +} \ No newline at end of file diff --git a/test/unit/util_test.go b/test/unit/util_test.go index 8653b8095..5614b51e0 100644 --- a/test/unit/util_test.go +++ b/test/unit/util_test.go @@ -1,6 +1,8 @@ package unit import ( + "fmt" + "net/url" "regexp" "testing" @@ -21,3 +23,16 @@ func mockRequestURL(t *testing.T, path string) *regexp.Regexp { func createMockClient(t *testing.T) *linodego.Client { return testutil.CreateMockClient(t, linodego.NewClient) } + +func formatMockAPIPath(format string, args ...any) string { + escapedArgs := make([]any, len(args)) + for i, arg := range args { + if typeStr, ok := arg.(string); ok { + arg = url.PathEscape(typeStr) + } + + escapedArgs[i] = arg + } + + return fmt.Sprintf(format, escapedArgs...) +}