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 }