sigs.k8s.io/external-dns@v0.14.1/provider/dnsimple/dnsimple_test.go (about)

     1  /*
     2  Copyright 2017 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package dnsimple
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"os"
    23  	"testing"
    24  
    25  	"github.com/dnsimple/dnsimple-go/dnsimple"
    26  	"github.com/stretchr/testify/assert"
    27  	"github.com/stretchr/testify/mock"
    28  	"github.com/stretchr/testify/require"
    29  
    30  	"sigs.k8s.io/external-dns/endpoint"
    31  	"sigs.k8s.io/external-dns/plan"
    32  	"sigs.k8s.io/external-dns/provider"
    33  )
    34  
    35  var (
    36  	mockProvider                dnsimpleProvider
    37  	dnsimpleListRecordsResponse dnsimple.ZoneRecordsResponse
    38  	dnsimpleListZonesResponse   dnsimple.ZonesResponse
    39  )
    40  
    41  func TestDnsimpleServices(t *testing.T) {
    42  	// Setup example responses
    43  	firstZone := dnsimple.Zone{
    44  		ID:        1,
    45  		AccountID: 12345,
    46  		Name:      "example.com",
    47  	}
    48  	secondZone := dnsimple.Zone{
    49  		ID:        2,
    50  		AccountID: 54321,
    51  		Name:      "example-beta.com",
    52  	}
    53  	zones := []dnsimple.Zone{firstZone, secondZone}
    54  	dnsimpleListZonesResponse = dnsimple.ZonesResponse{
    55  		Response: dnsimple.Response{Pagination: &dnsimple.Pagination{}},
    56  		Data:     zones,
    57  	}
    58  	firstRecord := dnsimple.ZoneRecord{
    59  		ID:       2,
    60  		ZoneID:   "example.com",
    61  		ParentID: 0,
    62  		Name:     "example",
    63  		Content:  "target",
    64  		TTL:      3600,
    65  		Priority: 0,
    66  		Type:     "CNAME",
    67  	}
    68  	secondRecord := dnsimple.ZoneRecord{
    69  		ID:       1,
    70  		ZoneID:   "example.com",
    71  		ParentID: 0,
    72  		Name:     "example-beta",
    73  		Content:  "127.0.0.1",
    74  		TTL:      3600,
    75  		Priority: 0,
    76  		Type:     "A",
    77  	}
    78  	thirdRecord := dnsimple.ZoneRecord{
    79  		ID:       3,
    80  		ZoneID:   "example.com",
    81  		ParentID: 0,
    82  		Name:     "custom-ttl",
    83  		Content:  "target",
    84  		TTL:      60,
    85  		Priority: 0,
    86  		Type:     "CNAME",
    87  	}
    88  	fourthRecord := dnsimple.ZoneRecord{
    89  		ID:       4,
    90  		ZoneID:   "example.com",
    91  		ParentID: 0,
    92  		Name:     "", // Apex domain A record
    93  		Content:  "127.0.0.1",
    94  		TTL:      3600,
    95  		Priority: 0,
    96  		Type:     "A",
    97  	}
    98  
    99  	records := []dnsimple.ZoneRecord{firstRecord, secondRecord, thirdRecord, fourthRecord}
   100  	dnsimpleListRecordsResponse = dnsimple.ZoneRecordsResponse{
   101  		Response: dnsimple.Response{Pagination: &dnsimple.Pagination{}},
   102  		Data:     records,
   103  	}
   104  
   105  	// Setup mock services
   106  	// Note: AnythingOfType doesn't work with interfaces https://github.com/stretchr/testify/issues/519
   107  	mockDNS := &mockDnsimpleZoneServiceInterface{}
   108  	mockDNS.On("ListZones", context.Background(), "1", &dnsimple.ZoneListOptions{ListOptions: dnsimple.ListOptions{Page: dnsimple.Int(1)}}).Return(&dnsimpleListZonesResponse, nil)
   109  	mockDNS.On("ListZones", context.Background(), "2", &dnsimple.ZoneListOptions{ListOptions: dnsimple.ListOptions{Page: dnsimple.Int(1)}}).Return(nil, fmt.Errorf("Account ID not found"))
   110  	mockDNS.On("ListRecords", context.Background(), "1", "example.com", &dnsimple.ZoneRecordListOptions{ListOptions: dnsimple.ListOptions{Page: dnsimple.Int(1)}}).Return(&dnsimpleListRecordsResponse, nil)
   111  	mockDNS.On("ListRecords", context.Background(), "1", "example-beta.com", &dnsimple.ZoneRecordListOptions{ListOptions: dnsimple.ListOptions{Page: dnsimple.Int(1)}}).Return(&dnsimple.ZoneRecordsResponse{Response: dnsimple.Response{Pagination: &dnsimple.Pagination{}}}, nil)
   112  
   113  	for _, record := range records {
   114  		recordName := record.Name
   115  		simpleRecord := dnsimple.ZoneRecordAttributes{
   116  			Name:    &recordName,
   117  			Type:    record.Type,
   118  			Content: record.Content,
   119  			TTL:     record.TTL,
   120  		}
   121  
   122  		dnsimpleRecordResponse := dnsimple.ZoneRecordsResponse{
   123  			Response: dnsimple.Response{Pagination: &dnsimple.Pagination{}},
   124  			Data:     []dnsimple.ZoneRecord{record},
   125  		}
   126  
   127  		mockDNS.On("ListRecords", context.Background(), "1", record.ZoneID, &dnsimple.ZoneRecordListOptions{Name: &recordName, ListOptions: dnsimple.ListOptions{Page: dnsimple.Int(1)}}).Return(&dnsimpleRecordResponse, nil)
   128  		mockDNS.On("CreateRecord", context.Background(), "1", record.ZoneID, simpleRecord).Return(&dnsimple.ZoneRecordResponse{}, nil)
   129  		mockDNS.On("DeleteRecord", context.Background(), "1", record.ZoneID, record.ID).Return(&dnsimple.ZoneRecordResponse{}, nil)
   130  		mockDNS.On("UpdateRecord", context.Background(), "1", record.ZoneID, record.ID, simpleRecord).Return(&dnsimple.ZoneRecordResponse{}, nil)
   131  	}
   132  
   133  	mockProvider = dnsimpleProvider{client: mockDNS}
   134  
   135  	// Run tests on mock services
   136  	t.Run("Zones", testDnsimpleProviderZones)
   137  	t.Run("Records", testDnsimpleProviderRecords)
   138  	t.Run("ApplyChanges", testDnsimpleProviderApplyChanges)
   139  	t.Run("ApplyChanges/SkipUnknownZone", testDnsimpleProviderApplyChangesSkipsUnknown)
   140  	t.Run("SuitableZone", testDnsimpleSuitableZone)
   141  	t.Run("GetRecordID", testDnsimpleGetRecordID)
   142  }
   143  
   144  func testDnsimpleProviderZones(t *testing.T) {
   145  	ctx := context.Background()
   146  	mockProvider.accountID = "1"
   147  	result, err := mockProvider.Zones(ctx)
   148  	assert.Nil(t, err)
   149  	validateDnsimpleZones(t, result, dnsimpleListZonesResponse.Data)
   150  
   151  	mockProvider.accountID = "2"
   152  	_, err = mockProvider.Zones(ctx)
   153  	assert.NotNil(t, err)
   154  }
   155  
   156  func testDnsimpleProviderRecords(t *testing.T) {
   157  	ctx := context.Background()
   158  	mockProvider.accountID = "1"
   159  	result, err := mockProvider.Records(ctx)
   160  	assert.Nil(t, err)
   161  	assert.Equal(t, len(dnsimpleListRecordsResponse.Data), len(result))
   162  
   163  	mockProvider.accountID = "2"
   164  	_, err = mockProvider.Records(ctx)
   165  	assert.NotNil(t, err)
   166  }
   167  
   168  func testDnsimpleProviderApplyChanges(t *testing.T) {
   169  	changes := &plan.Changes{}
   170  	changes.Create = []*endpoint.Endpoint{
   171  		{DNSName: "example.example.com", Targets: endpoint.Targets{"target"}, RecordType: endpoint.RecordTypeCNAME},
   172  		{DNSName: "custom-ttl.example.com", RecordTTL: 60, Targets: endpoint.Targets{"target"}, RecordType: endpoint.RecordTypeCNAME},
   173  	}
   174  	changes.Delete = []*endpoint.Endpoint{
   175  		{DNSName: "example-beta.example.com", Targets: endpoint.Targets{"127.0.0.1"}, RecordType: endpoint.RecordTypeA},
   176  	}
   177  	changes.UpdateNew = []*endpoint.Endpoint{
   178  		{DNSName: "example.example.com", Targets: endpoint.Targets{"target"}, RecordType: endpoint.RecordTypeCNAME},
   179  		{DNSName: "example.com", Targets: endpoint.Targets{"127.0.0.1"}, RecordType: endpoint.RecordTypeA},
   180  	}
   181  
   182  	mockProvider.accountID = "1"
   183  	err := mockProvider.ApplyChanges(context.Background(), changes)
   184  	if err != nil {
   185  		t.Errorf("Failed to apply changes: %v", err)
   186  	}
   187  }
   188  
   189  func testDnsimpleProviderApplyChangesSkipsUnknown(t *testing.T) {
   190  	changes := &plan.Changes{}
   191  	changes.Create = []*endpoint.Endpoint{
   192  		{DNSName: "example.not-included.com", Targets: endpoint.Targets{"dasd"}, RecordType: endpoint.RecordTypeCNAME},
   193  	}
   194  
   195  	mockProvider.accountID = "1"
   196  	err := mockProvider.ApplyChanges(context.Background(), changes)
   197  	if err != nil {
   198  		t.Errorf("Failed to ignore unknown zones: %v", err)
   199  	}
   200  }
   201  
   202  func testDnsimpleSuitableZone(t *testing.T) {
   203  	ctx := context.Background()
   204  	mockProvider.accountID = "1"
   205  	zones, err := mockProvider.Zones(ctx)
   206  	assert.Nil(t, err)
   207  
   208  	zone := dnsimpleSuitableZone("example-beta.example.com", zones)
   209  	assert.Equal(t, zone.Name, "example.com")
   210  }
   211  
   212  func TestNewDnsimpleProvider(t *testing.T) {
   213  	os.Setenv("DNSIMPLE_OAUTH", "xxxxxxxxxxxxxxxxxxxxxxxxxx")
   214  	_, err := NewDnsimpleProvider(endpoint.NewDomainFilter([]string{"example.com"}), provider.NewZoneIDFilter([]string{""}), true)
   215  	if err == nil {
   216  		t.Errorf("Expected to fail new provider on bad token")
   217  	}
   218  	os.Unsetenv("DNSIMPLE_OAUTH")
   219  	if err == nil {
   220  		t.Errorf("Expected to fail new provider on empty token")
   221  	}
   222  }
   223  
   224  func testDnsimpleGetRecordID(t *testing.T) {
   225  	var result int64
   226  	var err error
   227  
   228  	mockProvider.accountID = "1"
   229  	result, err = mockProvider.GetRecordID(context.Background(), "example.com", "example")
   230  	assert.Nil(t, err)
   231  	assert.Equal(t, int64(2), result)
   232  
   233  	result, err = mockProvider.GetRecordID(context.Background(), "example.com", "example-beta")
   234  	assert.Nil(t, err)
   235  	assert.Equal(t, int64(1), result)
   236  }
   237  
   238  func validateDnsimpleZones(t *testing.T, zones map[string]dnsimple.Zone, expected []dnsimple.Zone) {
   239  	require.Len(t, zones, len(expected))
   240  
   241  	for _, e := range expected {
   242  		assert.Equal(t, zones[int64ToString(e.ID)].Name, e.Name)
   243  	}
   244  }
   245  
   246  type mockDnsimpleZoneServiceInterface struct {
   247  	mock.Mock
   248  }
   249  
   250  func (_m *mockDnsimpleZoneServiceInterface) CreateRecord(ctx context.Context, accountID string, zoneID string, recordAttributes dnsimple.ZoneRecordAttributes) (*dnsimple.ZoneRecordResponse, error) {
   251  	args := _m.Called(ctx, accountID, zoneID, recordAttributes)
   252  	var r0 *dnsimple.ZoneRecordResponse
   253  
   254  	if args.Get(0) != nil {
   255  		r0 = args.Get(0).(*dnsimple.ZoneRecordResponse)
   256  	}
   257  
   258  	return r0, args.Error(1)
   259  }
   260  
   261  func (_m *mockDnsimpleZoneServiceInterface) DeleteRecord(ctx context.Context, accountID string, zoneID string, recordID int64) (*dnsimple.ZoneRecordResponse, error) {
   262  	args := _m.Called(ctx, accountID, zoneID, recordID)
   263  	var r0 *dnsimple.ZoneRecordResponse
   264  
   265  	if args.Get(0) != nil {
   266  		r0 = args.Get(0).(*dnsimple.ZoneRecordResponse)
   267  	}
   268  
   269  	return r0, args.Error(1)
   270  }
   271  
   272  func (_m *mockDnsimpleZoneServiceInterface) ListRecords(ctx context.Context, accountID string, zoneID string, options *dnsimple.ZoneRecordListOptions) (*dnsimple.ZoneRecordsResponse, error) {
   273  	args := _m.Called(ctx, accountID, zoneID, options)
   274  	var r0 *dnsimple.ZoneRecordsResponse
   275  
   276  	if args.Get(0) != nil {
   277  		r0 = args.Get(0).(*dnsimple.ZoneRecordsResponse)
   278  	}
   279  
   280  	return r0, args.Error(1)
   281  }
   282  
   283  func (_m *mockDnsimpleZoneServiceInterface) ListZones(ctx context.Context, accountID string, options *dnsimple.ZoneListOptions) (*dnsimple.ZonesResponse, error) {
   284  	args := _m.Called(ctx, accountID, options)
   285  	var r0 *dnsimple.ZonesResponse
   286  
   287  	if args.Get(0) != nil {
   288  		r0 = args.Get(0).(*dnsimple.ZonesResponse)
   289  	}
   290  
   291  	return r0, args.Error(1)
   292  }
   293  
   294  func (_m *mockDnsimpleZoneServiceInterface) UpdateRecord(ctx context.Context, accountID string, zoneID string, recordID int64, recordAttributes dnsimple.ZoneRecordAttributes) (*dnsimple.ZoneRecordResponse, error) {
   295  	args := _m.Called(ctx, accountID, zoneID, recordID, recordAttributes)
   296  	var r0 *dnsimple.ZoneRecordResponse
   297  
   298  	if args.Get(0) != nil {
   299  		r0 = args.Get(0).(*dnsimple.ZoneRecordResponse)
   300  	}
   301  
   302  	return r0, args.Error(1)
   303  }