external-dns/provider/civo/civo_test.go

1236 lines
30 KiB
Go

/*
Copyright 2020 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package civo
import (
"context"
"fmt"
"os"
"reflect"
"strings"
"testing"
"github.com/civo/civogo"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"sigs.k8s.io/external-dns/endpoint"
"sigs.k8s.io/external-dns/plan"
)
func TestNewCivoProvider(t *testing.T) {
_ = os.Setenv("CIVO_TOKEN", "xxxxxxxxxxxxxxx")
_, err := NewCivoProvider(endpoint.NewDomainFilter([]string{"test.civo.com"}), true)
require.NoError(t, err)
_ = os.Unsetenv("CIVO_TOKEN")
}
func TestNewCivoProviderNoToken(t *testing.T) {
_, err := NewCivoProvider(endpoint.NewDomainFilter([]string{"test.civo.com"}), true)
assert.Error(t, err)
assert.Equal(t, "no token found", err.Error())
}
func TestCivoProviderZones(t *testing.T) {
client, server, _ := civogo.NewClientForTesting(map[string]string{
"/v2/dns": `[
{"id": "12345", "account_id": "1", "name": "example.com"},
{"id": "12346", "account_id": "1", "name": "example.net"}
]`,
})
defer server.Close()
provider := &CivoProvider{
Client: *client,
}
expected, err := client.ListDNSDomains()
assert.NoError(t, err)
zones, err := provider.Zones(context.Background())
assert.NoError(t, err)
// Check if the return is a DNSDomain type
assert.Equal(t, reflect.TypeOf(zones), reflect.TypeOf(expected))
assert.ElementsMatch(t, zones, expected)
}
func TestCivoProviderZonesWithError(t *testing.T) {
client, server, _ := civogo.NewClientForTesting(map[string]string{
"/v2/dns-error": `[]`,
})
defer server.Close()
provider := &CivoProvider{
Client: *client,
}
_, err := provider.Zones(context.Background())
assert.Error(t, err)
}
func TestCivoProviderRecords(t *testing.T) {
client, server, _ := civogo.NewAdvancedClientForTesting([]civogo.ConfigAdvanceClientForTesting{
{
Method: "GET",
Value: []civogo.ValueAdvanceClientForTesting{
{
RequestBody: ``,
URL: "/v2/dns/12345/records",
ResponseBody: `[
{"id": "1", "domain_id":"12345", "account_id": "1", "name": "www", "type": "A", "value": "10.0.0.0", "ttl": 600},
{"id": "2", "account_id": "1", "domain_id":"12345", "name": "mail", "type": "A", "value": "10.0.0.1", "ttl": 600}
]`,
},
{
RequestBody: ``,
URL: "/v2/dns",
ResponseBody: `[
{"id": "12345", "account_id": "1", "name": "example.com"},
{"id": "12346", "account_id": "1", "name": "example.net"}
]`,
},
},
},
})
defer server.Close()
provider := &CivoProvider{
Client: *client,
domainFilter: endpoint.NewDomainFilter([]string{"example.com"}),
}
expected, err := client.ListDNSRecords("12345")
assert.NoError(t, err)
records, err := provider.Records(context.Background())
assert.NoError(t, err)
assert.Equal(t, strings.TrimSuffix(records[0].DNSName, ".example.com"), expected[0].Name)
assert.Equal(t, records[0].RecordType, string(expected[0].Type))
assert.Equal(t, int(records[0].RecordTTL), expected[0].TTL)
assert.Equal(t, strings.TrimSuffix(records[1].DNSName, ".example.com"), expected[1].Name)
assert.Equal(t, records[1].RecordType, string(expected[1].Type))
assert.Equal(t, int(records[1].RecordTTL), expected[1].TTL)
}
func TestCivoProviderRecordsWithError(t *testing.T) {
client, server, _ := civogo.NewAdvancedClientForTesting([]civogo.ConfigAdvanceClientForTesting{
{
Method: "GET",
Value: []civogo.ValueAdvanceClientForTesting{
{
RequestBody: ``,
URL: "/v2/dns/12345/records",
ResponseBody: `[
{"id": "1", "domain_id":"12345", "account_id": "1", "name": "", "type": "A", "value": "10.0.0.0", "ttl": 600},
{"id": "2", "account_id": "1", "domain_id":"12345", "name": "", "type": "A", "value": "10.0.0.1", "ttl": 600}
]`,
},
{
RequestBody: ``,
URL: "/v2/dns",
ResponseBody: `invalid-json-data`,
},
},
},
})
defer server.Close()
provider := &CivoProvider{
Client: *client,
domainFilter: endpoint.NewDomainFilter([]string{"example.com"}),
}
_, err := client.ListDNSRecords("12345")
assert.NoError(t, err)
endpoint, err := provider.Records(context.Background())
assert.Error(t, err)
assert.Nil(t, endpoint)
}
func TestCivoProviderWithoutRecords(t *testing.T) {
client, server, _ := civogo.NewClientForTesting(map[string]string{
"/v2/dns/12345/records": `[]`,
"/v2/dns": `[
{"id": "12345", "account_id": "1", "name": "example.com"},
{"id": "12346", "account_id": "1", "name": "example.net"}
]`,
})
defer server.Close()
provider := &CivoProvider{
Client: *client,
domainFilter: endpoint.NewDomainFilter([]string{"example.com"}),
}
records, err := provider.Records(context.Background())
assert.NoError(t, err)
assert.Empty(t, records)
}
func TestCivoProcessCreateActionsLogs(t *testing.T) {
t.Run("Logs Skipping Zone, no creates found", func(t *testing.T) {
zonesByID := map[string]civogo.DNSDomain{
"example.com": {
ID: "1",
AccountID: "1",
Name: "example.com",
},
}
recordsByZoneID := map[string][]civogo.DNSRecord{
"example.com": {
{
ID: "1",
AccountID: "1",
Name: "abc",
Value: "12.12.12.1",
Type: "A",
TTL: 600,
},
},
}
updateByZone := map[string][]*endpoint.Endpoint{
"example.com": {
endpoint.NewEndpoint("abc.example.com", endpoint.RecordTypeA, "1.2.3.4"),
},
}
var civoChanges CivoChanges
err := processCreateActions(zonesByID, recordsByZoneID, updateByZone, &civoChanges)
require.NoError(t, err)
assert.Len(t, civoChanges.Creates, 1)
assert.Empty(t, civoChanges.Deletes)
assert.Empty(t, civoChanges.Updates)
})
t.Run("Records found which should not exist", func(t *testing.T) {
zonesByID := map[string]civogo.DNSDomain{
"example.com": {
ID: "1",
AccountID: "1",
Name: "example.com",
},
}
recordsByZoneID := map[string][]civogo.DNSRecord{
"example.com": {},
}
updateByZone := map[string][]*endpoint.Endpoint{
"example.com": {},
}
var civoChanges CivoChanges
err := processCreateActions(zonesByID, recordsByZoneID, updateByZone, &civoChanges)
require.NoError(t, err)
assert.Empty(t, civoChanges.Creates)
assert.Empty(t, civoChanges.Creates)
assert.Empty(t, civoChanges.Updates)
})
}
func TestCivoProcessCreateActions(t *testing.T) {
zoneByID := map[string]civogo.DNSDomain{
"example.com": {
ID: "1",
AccountID: "1",
Name: "example.com",
},
}
recordsByZoneID := map[string][]civogo.DNSRecord{
"example.com": {
{
ID: "1",
AccountID: "1",
DNSDomainID: "1",
Name: "txt",
Value: "12.12.12.1",
Type: "A",
TTL: 600,
},
},
}
createsByZone := map[string][]*endpoint.Endpoint{
"example.com": {
endpoint.NewEndpoint("foo.example.com", endpoint.RecordTypeA, "1.2.3.4"),
endpoint.NewEndpoint("txt.example.com", endpoint.RecordTypeCNAME, "foo.example.com"),
},
}
var changes CivoChanges
err := processCreateActions(zoneByID, recordsByZoneID, createsByZone, &changes)
require.NoError(t, err)
assert.Len(t, changes.Creates, 2)
assert.Empty(t, changes.Updates)
assert.Empty(t, changes.Deletes)
expectedCreates := []*CivoChangeCreate{
{
Domain: civogo.DNSDomain{
ID: "1",
AccountID: "1",
Name: "example.com",
},
Options: &civogo.DNSRecordConfig{
Type: "A",
Name: "foo",
Value: "1.2.3.4",
},
},
{
Domain: civogo.DNSDomain{
ID: "1",
AccountID: "1",
Name: "example.com",
},
Options: &civogo.DNSRecordConfig{
Type: "CNAME",
Name: "txt",
Value: "foo.example.com",
},
},
}
if !elementsMatch(t, expectedCreates, changes.Creates) {
assert.Failf(t, "diff: %s", cmp.Diff(expectedCreates, changes.Creates))
}
}
func TestCivoProcessCreateActionsWithError(t *testing.T) {
zoneByID := map[string]civogo.DNSDomain{
"example.com": {
ID: "1",
AccountID: "1",
Name: "example.com",
},
}
recordsByZoneID := map[string][]civogo.DNSRecord{
"example.com": {
{
ID: "1",
AccountID: "1",
DNSDomainID: "1",
Name: "txt",
Value: "12.12.12.1",
Type: "A",
TTL: 600,
},
},
}
createsByZone := map[string][]*endpoint.Endpoint{
"example.com": {
endpoint.NewEndpoint("foo.example.com", "AAAA", "1.2.3.4"),
endpoint.NewEndpoint("txt.example.com", endpoint.RecordTypeCNAME, "foo.example.com"),
},
}
var changes CivoChanges
err := processCreateActions(zoneByID, recordsByZoneID, createsByZone, &changes)
require.Error(t, err)
assert.Equal(t, "invalid Record Type: AAAA", err.Error())
}
func TestCivoProcessUpdateActionsWithError(t *testing.T) {
zoneByID := map[string]civogo.DNSDomain{
"example.com": {
ID: "1",
AccountID: "1",
Name: "example.com",
},
}
recordsByZoneID := map[string][]civogo.DNSRecord{
"example.com": {
{
ID: "1",
AccountID: "1",
DNSDomainID: "1",
Name: "txt",
Value: "12.12.12.1",
Type: "A",
TTL: 600,
},
},
}
updatesByZone := map[string][]*endpoint.Endpoint{
"example.com": {
endpoint.NewEndpoint("foo.example.com", "AAAA", "1.2.3.4"),
endpoint.NewEndpoint("txt.example.com", endpoint.RecordTypeCNAME, "foo.example.com"),
},
}
var changes CivoChanges
err := processUpdateActions(zoneByID, recordsByZoneID, updatesByZone, &changes)
require.Error(t, err)
}
func TestCivoProcessUpdateActions(t *testing.T) {
zoneByID := map[string]civogo.DNSDomain{
"example.com": {
ID: "1",
AccountID: "1",
Name: "example.com",
},
}
recordsByZoneID := map[string][]civogo.DNSRecord{
"example.com": {
{
ID: "1",
AccountID: "1",
DNSDomainID: "1",
Name: "txt",
Value: "1.2.3.4",
Type: "A",
TTL: 600,
},
{
ID: "2",
AccountID: "1",
DNSDomainID: "1",
Name: "foo",
Value: "foo.example.com",
Type: "CNAME",
TTL: 600,
},
{
ID: "3",
AccountID: "1",
DNSDomainID: "1",
Name: "bar",
Value: "10.10.10.1",
Type: "A",
TTL: 600,
},
},
}
updatesByZone := map[string][]*endpoint.Endpoint{
"example.com": {
endpoint.NewEndpoint("txt.example.com", endpoint.RecordTypeA, "10.20.30.40"),
endpoint.NewEndpoint("foo.example.com", endpoint.RecordTypeCNAME, "bar.example.com"),
},
}
var changes CivoChanges
err := processUpdateActions(zoneByID, recordsByZoneID, updatesByZone, &changes)
require.NoError(t, err)
assert.Len(t, changes.Creates, 2)
assert.Empty(t, changes.Updates)
assert.Len(t, changes.Deletes, 2)
expectedUpdate := []*CivoChangeCreate{
{
Domain: civogo.DNSDomain{
ID: "1",
AccountID: "1",
Name: "example.com",
},
Options: &civogo.DNSRecordConfig{
Type: "A",
Name: "txt",
Value: "10.20.30.40",
},
},
{
Domain: civogo.DNSDomain{
ID: "1",
AccountID: "1",
Name: "example.com",
},
Options: &civogo.DNSRecordConfig{
Type: "CNAME",
Name: "foo",
Value: "bar.example.com",
},
},
}
if !elementsMatch(t, expectedUpdate, changes.Creates) {
assert.Failf(t, "diff: %s", cmp.Diff(expectedUpdate, changes.Creates))
}
expectedDelete := []*CivoChangeDelete{
{
Domain: civogo.DNSDomain{
ID: "1",
AccountID: "1",
Name: "example.com",
},
DomainRecord: civogo.DNSRecord{
ID: "1",
AccountID: "1",
DNSDomainID: "1",
Name: "txt",
Value: "1.2.3.4",
Type: "A",
Priority: 0,
TTL: 600,
},
},
{
Domain: civogo.DNSDomain{
ID: "1",
AccountID: "1",
Name: "example.com",
},
DomainRecord: civogo.DNSRecord{
ID: "2",
AccountID: "1",
DNSDomainID: "1",
Name: "foo",
Value: "foo.example.com",
Type: "CNAME",
Priority: 0,
TTL: 600,
},
},
}
if !elementsMatch(t, expectedDelete, changes.Deletes) {
assert.Failf(t, "diff: %s", cmp.Diff(expectedDelete, changes.Deletes))
}
}
func TestCivoProcessDeleteAction(t *testing.T) {
zoneByID := map[string]civogo.DNSDomain{
"example.com": {
ID: "1",
AccountID: "1",
Name: "example.com",
},
}
recordsByZoneID := map[string][]civogo.DNSRecord{
"example.com": {
{
ID: "1",
AccountID: "1",
DNSDomainID: "1",
Name: "txt",
Value: "1.2.3.4",
Type: "A",
TTL: 600,
},
{
ID: "2",
AccountID: "1",
DNSDomainID: "1",
Name: "foo",
Value: "5.6.7.8",
Type: "A",
TTL: 600,
},
{
ID: "3",
AccountID: "1",
DNSDomainID: "1",
Name: "bar",
Value: "10.10.10.1",
Type: "A",
TTL: 600,
},
},
}
deleteByDomain := map[string][]*endpoint.Endpoint{
"example.com": {
endpoint.NewEndpoint("txt.example.com", endpoint.RecordTypeA, "1.2.3.4"),
endpoint.NewEndpoint("foo.example.com", endpoint.RecordTypeA, "5.6.7.8"),
},
}
var changes CivoChanges
err := processDeleteActions(zoneByID, recordsByZoneID, deleteByDomain, &changes)
require.NoError(t, err)
assert.Empty(t, changes.Creates)
assert.Empty(t, changes.Updates)
assert.Len(t, changes.Deletes, 2)
expectedDelete := []*CivoChangeDelete{
{
Domain: civogo.DNSDomain{
ID: "1",
AccountID: "1",
Name: "example.com",
},
DomainRecord: civogo.DNSRecord{
ID: "1",
AccountID: "1",
DNSDomainID: "1",
Name: "txt",
Value: "1.2.3.4",
Type: "A",
TTL: 600,
},
},
{
Domain: civogo.DNSDomain{
ID: "1",
AccountID: "1",
Name: "example.com",
},
DomainRecord: civogo.DNSRecord{
ID: "2",
AccountID: "1",
DNSDomainID: "1",
Type: "A",
Name: "foo",
Value: "5.6.7.8",
TTL: 600,
},
},
}
if !elementsMatch(t, expectedDelete, changes.Deletes) {
assert.Failf(t, "diff: %s", cmp.Diff(expectedDelete, changes.Deletes))
}
}
func TestCivoApplyChanges(t *testing.T) {
client, server, _ := civogo.NewAdvancedClientForTesting([]civogo.ConfigAdvanceClientForTesting{
{
Method: "GET",
Value: []civogo.ValueAdvanceClientForTesting{
{
RequestBody: "",
URL: "/v2/dns",
ResponseBody: `[{"id": "12345", "account_id": "1", "name": "example.com"}]`,
},
{
RequestBody: "",
URL: "/v2/dns/12345/records",
ResponseBody: `[]`,
},
},
},
})
defer server.Close()
changes := &plan.Changes{}
provider := &CivoProvider{
Client: *client,
}
changes.Create = []*endpoint.Endpoint{
{DNSName: "new.ext-dns-test.example.com", Targets: endpoint.Targets{"target"}, RecordType: endpoint.RecordTypeA},
{DNSName: "new.ext-dns-test-with-ttl.example.com", Targets: endpoint.Targets{"target"}, RecordType: endpoint.RecordTypeA, RecordTTL: 100},
}
changes.Delete = []*endpoint.Endpoint{{DNSName: "foobar.ext-dns-test.example.com", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"target"}}}
changes.Update = []*plan.Update{
{
Old: &endpoint.Endpoint{DNSName: "foobar.ext-dns-test.example.de", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"target-old"}},
New: &endpoint.Endpoint{DNSName: "foobar.ext-dns-test.foo.com", Targets: endpoint.Targets{"target-new"}, RecordType: endpoint.RecordTypeCNAME, RecordTTL: 100},
},
}
err := provider.ApplyChanges(context.Background(), changes)
assert.NoError(t, err)
}
func TestCivoApplyChangesError(t *testing.T) {
client, server, _ := civogo.NewAdvancedClientForTesting([]civogo.ConfigAdvanceClientForTesting{
{
Method: "GET",
Value: []civogo.ValueAdvanceClientForTesting{
{
RequestBody: "",
URL: "/v2/dns",
ResponseBody: `[{"id": "12345", "account_id": "1", "name": "example.com"}]`,
},
{
RequestBody: "",
URL: "/v2/dns/12345/records",
ResponseBody: `[]`,
},
},
},
})
defer server.Close()
provider := &CivoProvider{
Client: *client,
}
cases := []struct {
Name string
changes *plan.Changes
}{
{
Name: "invalid record type from processCreateActions",
changes: &plan.Changes{
Create: []*endpoint.Endpoint{
endpoint.NewEndpoint("bad.example.com", "AAAA", "1.2.3.4"),
},
},
},
{
Name: "invalid record type from processUpdateActions",
changes: &plan.Changes{
Update: []*plan.Update{
{
Old: endpoint.NewEndpoint("bad.example.com", "AAAA", "1.2.3.4"),
New: endpoint.NewEndpoint("bad.example.com", "AAAA", "5.6.7.8"),
},
},
},
},
}
for _, tt := range cases {
t.Run(tt.Name, func(t *testing.T) {
err := provider.ApplyChanges(context.Background(), tt.changes)
assert.Equal(t, "invalid Record Type: AAAA", string(err.Error()))
})
}
}
func TestCivoProviderFetchZones(t *testing.T) {
client, server, _ := civogo.NewClientForTesting(map[string]string{
"/v2/dns": `[
{"id": "12345", "account_id": "1", "name": "example.com"},
{"id": "12346", "account_id": "1", "name": "example.net"}
]`,
})
defer server.Close()
provider := &CivoProvider{
Client: *client,
}
expected, err := client.ListDNSDomains()
if err != nil {
t.Errorf("should not fail, %s", err)
}
zones, err := provider.fetchZones(context.Background())
if err != nil {
t.Fatal(err)
}
assert.ElementsMatch(t, zones, expected)
}
func TestCivoProviderFetchZonesWithFilter(t *testing.T) {
client, server, _ := civogo.NewClientForTesting(map[string]string{
"/v2/dns": `[
{"id": "12345", "account_id": "1", "name": "example.com"},
{"id": "12346", "account_id": "1", "name": "example.net"}
]`,
})
defer server.Close()
provider := &CivoProvider{
Client: *client,
domainFilter: endpoint.NewDomainFilter([]string{".com"}),
}
expected := []civogo.DNSDomain{
{ID: "12345", Name: "example.com", AccountID: "1"},
}
actual, err := provider.fetchZones(context.Background())
if err != nil {
t.Fatal(err)
}
assert.ElementsMatch(t, expected, actual)
}
func TestCivoProviderFetchRecords(t *testing.T) {
client, server, _ := civogo.NewClientForTesting(map[string]string{
"/v2/dns/12345/records": `[
{"id": "1", "domain_id":"12345", "account_id": "1", "name": "www", "type": "A", "value": "10.0.0.0", "ttl": 600},
{"id": "2", "account_id": "1", "domain_id":"12345", "name": "mail", "type": "A", "value": "10.0.0.1", "ttl": 600}
]`,
})
defer server.Close()
provider := &CivoProvider{
Client: *client,
}
expected, err := client.ListDNSRecords("12345")
assert.NoError(t, err)
actual, err := provider.fetchRecords(context.Background(), "12345")
assert.NoError(t, err)
assert.ElementsMatch(t, expected, actual)
}
func TestCivoProviderFetchRecordsWithError(t *testing.T) {
client, server, _ := civogo.NewClientForTesting(map[string]string{
"/v2/dns/12345/records": `[
{"id": "1", "domain_id":"12345", "account_id": "1", "name": "www", "type": "A", "value": "10.0.0.0", "ttl": 600},
{"id": "2", "account_id": "1", "domain_id":"12345", "name": "mail", "type": "A", "value": "10.0.0.1", "ttl": 600}
]`,
})
defer server.Close()
provider := &CivoProvider{
Client: *client,
}
_, err := provider.fetchRecords(context.Background(), "235698")
assert.Error(t, err)
}
func TestCivo_getStrippedRecordName(t *testing.T) {
assert.Empty(t, getStrippedRecordName(civogo.DNSDomain{
Name: "foo.com",
}, endpoint.Endpoint{
DNSName: "foo.com",
}))
assert.Equal(t, "api", getStrippedRecordName(civogo.DNSDomain{
Name: "foo.com",
}, endpoint.Endpoint{
DNSName: "api.foo.com",
}))
}
func TestCivo_convertRecordType(t *testing.T) {
record, err := convertRecordType("A")
recordA := civogo.DNSRecordType(civogo.DNSRecordTypeA)
require.NoError(t, err)
assert.Equal(t, recordA, record)
record, err = convertRecordType("CNAME")
recordCName := civogo.DNSRecordType(civogo.DNSRecordTypeCName)
require.NoError(t, err)
assert.Equal(t, recordCName, record)
record, err = convertRecordType("TXT")
recordTXT := civogo.DNSRecordType(civogo.DNSRecordTypeTXT)
require.NoError(t, err)
assert.Equal(t, recordTXT, record)
record, err = convertRecordType("SRV")
recordSRV := civogo.DNSRecordType(civogo.DNSRecordTypeSRV)
require.NoError(t, err)
assert.Equal(t, recordSRV, record)
_, err = convertRecordType("INVALID")
require.Error(t, err)
assert.Equal(t, "invalid Record Type: INVALID", err.Error())
}
func TestCivoProviderGetRecordID(t *testing.T) {
zone := civogo.DNSDomain{
ID: "12345",
Name: "test.com",
}
record := []civogo.DNSRecord{{
ID: "1",
Type: "A",
Name: "www",
Value: "10.0.0.0",
DNSDomainID: "12345",
TTL: 600,
}, {
ID: "2",
Type: "A",
Name: "api",
Value: "10.0.0.1",
DNSDomainID: "12345",
TTL: 600,
}}
endPoint := endpoint.Endpoint{DNSName: "www.test.com", Targets: endpoint.Targets{"10.0.0.0"}, RecordType: "A"}
id := getRecordID(record, zone, endPoint)
assert.Equal(t, id[0].ID, record[0].ID)
}
func TestCivo_submitChangesCreate(t *testing.T) {
client, server, _ := civogo.NewAdvancedClientForTesting([]civogo.ConfigAdvanceClientForTesting{
{
Method: "POST",
Value: []civogo.ValueAdvanceClientForTesting{
{
RequestBody: `{"type":"MX","name":"mail","value":"10.0.0.1","priority":10,"ttl":600}`,
URL: "/v2/dns/12345/records",
ResponseBody: `{
"id": "76cc107f-fbef-4e2b-b97f-f5d34f4075d3",
"account_id": "1",
"domain_id": "12345",
"name": "mail",
"value": "10.0.0.1",
"type": "MX",
"priority": 10,
"ttl": 600
}`,
},
},
},
{
Method: "DELETE",
Value: []civogo.ValueAdvanceClientForTesting{
{
URL: "/v2/dns/12345/records/76cc107f-fbef-4e2b-b97f-f5d34f4075d3",
ResponseBody: `{"result": "success"}`,
},
{
URL: "/v2/dns/12345/records/error-record-id",
ResponseBody: `{"result": "error", "error": "failed to delete record"}`,
},
},
},
{
Method: "PUT",
Value: []civogo.ValueAdvanceClientForTesting{
{
RequestBody: `{"type":"MX","name":"mail","value":"10.0.0.2","priority":10,"ttl":600}`,
URL: "/v2/dns/12345/records/76cc107f-fbef-4e2b-b97f-f5d34f4075d3",
ResponseBody: `{
"id": "76cc107f-fbef-4e2b-b97f-f5d34f4075d3",
"account_id": "1",
"domain_id": "12345",
"name": "mail",
"value": "10.0.0.2",
"type": "MX",
"priority": 10,
"ttl": 600
}`,
},
{
RequestBody: `{"type":"MX","name":"mail","value":"10.0.0.3","priority":10,"ttl":600}`,
URL: "/v2/dns/12345/records/error-record-id",
ResponseBody: `{"result": "error", "error": "failed to update record"}`,
},
},
},
})
defer server.Close()
provider := &CivoProvider{
Client: *client,
DryRun: true,
}
cases := []struct {
name string
changes *CivoChanges
expectedResult error
}{
{
name: "changes slice is empty",
changes: &CivoChanges{},
expectedResult: nil,
},
{
name: "changes slice has changes and update changes",
changes: &CivoChanges{
Creates: []*CivoChangeCreate{
{
Domain: civogo.DNSDomain{
ID: "12345",
AccountID: "1",
Name: "example.com",
},
Options: &civogo.DNSRecordConfig{
Type: "MX",
Name: "mail",
Value: "10.0.0.1",
Priority: 10,
TTL: 600,
},
},
},
Updates: []*CivoChangeUpdate{
{
Domain: civogo.DNSDomain{
ID: "12345",
AccountID: "2",
Name: "example.org",
},
},
{
Domain: civogo.DNSDomain{
ID: "67890",
AccountID: "3",
Name: "example.COM",
},
},
},
},
expectedResult: nil,
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
err := provider.submitChanges(context.Background(), *c.changes)
assert.NoError(t, err)
})
}
}
func TestCivo_submitChangesDelete(t *testing.T) {
client, server, _ := civogo.NewClientForTesting(map[string]string{
"/v2/dns/12345/records/76cc107f-fbef-4e2b-b97f-f5d34f4075d3": `{"result": "success"}`,
})
defer server.Close()
provider := &CivoProvider{
Client: *client,
DryRun: false,
}
changes := CivoChanges{
Deletes: []*CivoChangeDelete{
{
Domain: civogo.DNSDomain{ID: "12345", AccountID: "1", Name: "example.com"},
DomainRecord: civogo.DNSRecord{
ID: "76cc107f-fbef-4e2b-b97f-f5d34f4075d3",
AccountID: "1",
DNSDomainID: "12345",
Name: "mail",
Value: "10.0.0.2",
Type: "MX",
Priority: 10,
TTL: 600,
},
},
},
}
err := provider.submitChanges(context.Background(), changes)
assert.NoError(t, err)
}
func TestCivoChangesEmpty(t *testing.T) {
// Test empty CivoChanges
c := &CivoChanges{}
assert.True(t, c.Empty())
// Test CivoChanges with Creates
c = &CivoChanges{
Creates: []*CivoChangeCreate{
{
Domain: civogo.DNSDomain{
ID: "12345",
AccountID: "1",
Name: "example.com",
},
Options: &civogo.DNSRecordConfig{
Type: civogo.DNSRecordTypeA,
Name: "www",
Value: "192.1.1.1",
Priority: 0,
TTL: 600,
},
},
},
}
assert.False(t, c.Empty())
// Test CivoChanges with Updates
c = &CivoChanges{
Updates: []*CivoChangeUpdate{
{
Domain: civogo.DNSDomain{
ID: "12345",
AccountID: "1",
Name: "example.com",
},
DomainRecord: civogo.DNSRecord{
ID: "76cc107f-fbef-4e2b-b97f-f5d34f4075d3",
AccountID: "1",
DNSDomainID: "12345",
Name: "mail",
Value: "192.168.1.2",
Type: "MX",
Priority: 10,
TTL: 600,
},
Options: civogo.DNSRecordConfig{
Type: "MX",
Name: "mail",
Value: "192.168.1.3",
Priority: 10,
TTL: 600,
},
},
},
}
assert.False(t, c.Empty())
// Test CivoChanges with Deletes
c = &CivoChanges{
Deletes: []*CivoChangeDelete{
{
Domain: civogo.DNSDomain{
ID: "12345",
AccountID: "1",
Name: "example.com",
},
DomainRecord: civogo.DNSRecord{
ID: "76cc107f-fbef-4e2b-b97f-f5d34f4075d3",
AccountID: "1",
DNSDomainID: "12345",
Name: "mail",
Value: "192.168.1.3",
Type: "MX",
Priority: 10,
TTL: 600,
},
},
},
}
assert.False(t, c.Empty())
// Test CivoChanges with Creates, Updates, and Deletes
c = &CivoChanges{
Creates: []*CivoChangeCreate{
{
Domain: civogo.DNSDomain{
ID: "12345",
AccountID: "1",
Name: "example.com",
},
Options: &civogo.DNSRecordConfig{
Type: civogo.DNSRecordTypeA,
Name: "www",
Value: "192.1.1.1",
Priority: 0,
TTL: 600,
},
},
},
Updates: []*CivoChangeUpdate{
{
Domain: civogo.DNSDomain{
ID: "12345",
AccountID: "1",
Name: "example.com",
},
DomainRecord: civogo.DNSRecord{
ID: "76cc107f-fbef-4e2b-b97f-f5d34f4075d3",
AccountID: "1",
DNSDomainID: "12345",
Name: "mail",
Value: "192.168.1.2",
Type: "MX",
Priority: 10,
TTL: 600,
},
Options: civogo.DNSRecordConfig{
Type: "MX",
Name: "mail",
Value: "192.168.1.3",
Priority: 10,
TTL: 600,
},
},
},
Deletes: []*CivoChangeDelete{
{
Domain: civogo.DNSDomain{
ID: "12345",
AccountID: "1",
Name: "example.com",
},
DomainRecord: civogo.DNSRecord{
ID: "76cc107f-fbef-4e2b-b97f-f5d34f4075d3",
AccountID: "1",
DNSDomainID: "12345",
Name: "mail",
Value: "192.168.1.3",
Type: "MX",
Priority: 10,
TTL: 600,
},
},
},
}
assert.False(t, c.Empty())
}
// This function is an adapted copy of the testify package's ElementsMatch function with the
// call to ObjectsAreEqual replaced with cmp.Equal which better handles struct's with pointers to
// other structs. It also ignores ordering when comparing unlike cmp.Equal.
func elementsMatch(t *testing.T, listA, listB interface{}, msgAndArgs ...interface{}) bool {
if listA == nil && listB == nil {
return true
} else if listA == nil {
return isEmpty(listB)
} else if listB == nil {
return isEmpty(listA)
}
aKind := reflect.TypeOf(listA).Kind()
bKind := reflect.TypeOf(listB).Kind()
if aKind != reflect.Array && aKind != reflect.Slice {
return assert.Fail(t, fmt.Sprintf("%q has an unsupported type %s", listA, aKind), msgAndArgs...)
}
if bKind != reflect.Array && bKind != reflect.Slice {
return assert.Fail(t, fmt.Sprintf("%q has an unsupported type %s", listB, bKind), msgAndArgs...)
}
aValue := reflect.ValueOf(listA)
bValue := reflect.ValueOf(listB)
aLen := aValue.Len()
bLen := bValue.Len()
if aLen != bLen {
return assert.Fail(t, fmt.Sprintf("lengths don't match: %d != %d", aLen, bLen), msgAndArgs...)
}
// Mark indexes in bValue that we already used
visited := make([]bool, bLen)
for i := 0; i < aLen; i++ {
element := aValue.Index(i).Interface()
found := false
for j := 0; j < bLen; j++ {
if visited[j] {
continue
}
if cmp.Equal(bValue.Index(j).Interface(), element) {
visited[j] = true
found = true
break
}
}
if !found {
return assert.Fail(t, fmt.Sprintf("element %s appears more times in %s than in %s", element, aValue, bValue), msgAndArgs...)
}
}
return true
}
func isEmpty(xs interface{}) bool {
if xs != nil {
objValue := reflect.ValueOf(xs)
return objValue.Len() == 0
}
return true
}