1package linode 2 3import ( 4 "encoding/json" 5 "fmt" 6 "net/http" 7 "net/http/httptest" 8 "os" 9 "testing" 10 "time" 11 12 "github.com/go-acme/lego/v3/platform/tester" 13 "github.com/stretchr/testify/assert" 14 "github.com/stretchr/testify/require" 15 "github.com/timewasted/linode" 16 "github.com/timewasted/linode/dns" 17) 18 19type ( 20 apiResponse struct { 21 Action string `json:"ACTION"` 22 Data interface{} `json:"DATA"` 23 Errors []linode.ResponseError `json:"ERRORARRAY"` 24 } 25 MockResponse struct { 26 Response interface{} 27 Errors []linode.ResponseError 28 } 29 MockResponseMap map[string]MockResponse 30) 31 32var envTest = tester.NewEnvTest(EnvAPIKey) 33 34func newMockServer(responses MockResponseMap) *httptest.Server { 35 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 36 // Ensure that we support the requested action. 37 action := r.URL.Query().Get("api_action") 38 resp, ok := responses[action] 39 if !ok { 40 http.Error(w, fmt.Sprintf("Unsupported mock action: %q", action), http.StatusInternalServerError) 41 return 42 } 43 44 // Build the response that the server will return. 45 response := apiResponse{ 46 Action: action, 47 Data: resp.Response, 48 Errors: resp.Errors, 49 } 50 51 rawResponse, err := json.Marshal(response) 52 if err != nil { 53 http.Error(w, fmt.Sprintf("Failed to JSON encode response: %v", err), http.StatusInternalServerError) 54 return 55 } 56 57 // Send the response. 58 w.Header().Set("Content-Type", "application/json") 59 w.WriteHeader(http.StatusOK) 60 _, err = w.Write(rawResponse) 61 if err != nil { 62 http.Error(w, err.Error(), http.StatusInternalServerError) 63 return 64 } 65 })) 66 67 time.Sleep(100 * time.Millisecond) 68 return srv 69} 70 71func TestNewDNSProvider(t *testing.T) { 72 testCases := []struct { 73 desc string 74 envVars map[string]string 75 expected string 76 }{ 77 { 78 desc: "success", 79 envVars: map[string]string{ 80 EnvAPIKey: "123", 81 }, 82 }, 83 { 84 desc: "missing api key", 85 envVars: map[string]string{ 86 EnvAPIKey: "", 87 }, 88 expected: "linode: some credentials information are missing: LINODE_API_KEY", 89 }, 90 } 91 92 for _, test := range testCases { 93 t.Run(test.desc, func(t *testing.T) { 94 defer envTest.RestoreEnv() 95 envTest.ClearEnv() 96 97 envTest.Apply(test.envVars) 98 99 p, err := NewDNSProvider() 100 101 if len(test.expected) == 0 { 102 require.NoError(t, err) 103 require.NotNil(t, p) 104 require.NotNil(t, p.config) 105 require.NotNil(t, p.client) 106 } else { 107 require.EqualError(t, err, test.expected) 108 } 109 }) 110 } 111} 112 113func TestNewDNSProviderConfig(t *testing.T) { 114 testCases := []struct { 115 desc string 116 apiKey string 117 expected string 118 }{ 119 { 120 desc: "success", 121 apiKey: "123", 122 }, 123 { 124 desc: "missing credentials", 125 expected: "linode: credentials missing", 126 }, 127 } 128 129 for _, test := range testCases { 130 t.Run(test.desc, func(t *testing.T) { 131 config := NewDefaultConfig() 132 config.APIKey = test.apiKey 133 134 p, err := NewDNSProviderConfig(config) 135 136 if len(test.expected) == 0 { 137 require.NoError(t, err) 138 require.NotNil(t, p) 139 require.NotNil(t, p.config) 140 require.NotNil(t, p.client) 141 } else { 142 require.EqualError(t, err, test.expected) 143 } 144 }) 145 } 146} 147 148func TestDNSProvider_Present(t *testing.T) { 149 defer envTest.RestoreEnv() 150 os.Setenv(EnvAPIKey, "testing") 151 152 p, err := NewDNSProvider() 153 require.NoError(t, err) 154 155 domain := "example.com" 156 keyAuth := "dGVzdGluZw==" 157 158 testCases := []struct { 159 desc string 160 mockResponses MockResponseMap 161 expectedError string 162 }{ 163 { 164 desc: "success", 165 mockResponses: MockResponseMap{ 166 "domain.list": MockResponse{ 167 Response: []dns.Domain{ 168 { 169 Domain: domain, 170 DomainID: 1234, 171 }, 172 }, 173 }, 174 "domain.resource.create": MockResponse{ 175 Response: dns.ResourceResponse{ 176 ResourceID: 1234, 177 }, 178 }, 179 }, 180 }, 181 { 182 desc: "NoDomain", 183 mockResponses: MockResponseMap{ 184 "domain.list": MockResponse{ 185 Response: []dns.Domain{{ 186 Domain: "foobar.com", 187 DomainID: 1234, 188 }}, 189 }, 190 }, 191 expectedError: "dns: requested domain not found", 192 }, 193 { 194 desc: "CreateFailed", 195 mockResponses: MockResponseMap{ 196 "domain.list": MockResponse{ 197 Response: []dns.Domain{ 198 { 199 Domain: domain, 200 DomainID: 1234, 201 }, 202 }, 203 }, 204 "domain.resource.create": MockResponse{ 205 Response: nil, 206 Errors: []linode.ResponseError{ 207 { 208 Code: 1234, 209 Message: "Failed to create domain resource", 210 }, 211 }, 212 }, 213 }, 214 expectedError: "Failed to create domain resource", 215 }, 216 } 217 218 for _, test := range testCases { 219 t.Run(test.desc, func(t *testing.T) { 220 server := newMockServer(test.mockResponses) 221 defer server.Close() 222 223 p.client.ToLinode().SetEndpoint(server.URL) 224 225 err = p.Present(domain, "", keyAuth) 226 if len(test.expectedError) == 0 { 227 assert.NoError(t, err) 228 } else { 229 assert.EqualError(t, err, test.expectedError) 230 } 231 }) 232 } 233} 234 235func TestDNSProvider_CleanUp(t *testing.T) { 236 defer envTest.RestoreEnv() 237 os.Setenv(EnvAPIKey, "testing") 238 239 p, err := NewDNSProvider() 240 require.NoError(t, err) 241 242 domain := "example.com" 243 keyAuth := "dGVzdGluZw==" 244 245 testCases := []struct { 246 desc string 247 mockResponses MockResponseMap 248 expectedError string 249 }{ 250 { 251 desc: "success", 252 mockResponses: MockResponseMap{ 253 "domain.list": MockResponse{ 254 Response: []dns.Domain{ 255 { 256 Domain: domain, 257 DomainID: 1234, 258 }, 259 }, 260 }, 261 "domain.resource.list": MockResponse{ 262 Response: []dns.Resource{ 263 { 264 DomainID: 1234, 265 Name: "_acme-challenge", 266 ResourceID: 1234, 267 Target: "ElbOJKOkFWiZLQeoxf-wb3IpOsQCdvoM0y_wn0TEkxM", 268 Type: "TXT", 269 }, 270 }, 271 }, 272 "domain.resource.delete": MockResponse{ 273 Response: dns.ResourceResponse{ 274 ResourceID: 1234, 275 }, 276 }, 277 }, 278 }, 279 { 280 desc: "NoDomain", 281 mockResponses: MockResponseMap{ 282 "domain.list": MockResponse{ 283 Response: []dns.Domain{ 284 { 285 Domain: "foobar.com", 286 DomainID: 1234, 287 }, 288 }, 289 }, 290 }, 291 expectedError: "dns: requested domain not found", 292 }, 293 { 294 desc: "DeleteFailed", 295 mockResponses: MockResponseMap{ 296 "domain.list": MockResponse{ 297 Response: []dns.Domain{ 298 { 299 Domain: domain, 300 DomainID: 1234, 301 }, 302 }, 303 }, 304 "domain.resource.list": MockResponse{ 305 Response: []dns.Resource{ 306 { 307 DomainID: 1234, 308 Name: "_acme-challenge", 309 ResourceID: 1234, 310 Target: "ElbOJKOkFWiZLQeoxf-wb3IpOsQCdvoM0y_wn0TEkxM", 311 Type: "TXT", 312 }, 313 }, 314 }, 315 "domain.resource.delete": MockResponse{ 316 Response: nil, 317 Errors: []linode.ResponseError{ 318 { 319 Code: 1234, 320 Message: "Failed to delete domain resource", 321 }, 322 }, 323 }, 324 }, 325 expectedError: "Failed to delete domain resource", 326 }, 327 } 328 329 for _, test := range testCases { 330 t.Run(test.desc, func(t *testing.T) { 331 server := newMockServer(test.mockResponses) 332 defer server.Close() 333 334 p.client.ToLinode().SetEndpoint(server.URL) 335 336 err = p.CleanUp(domain, "", keyAuth) 337 if len(test.expectedError) == 0 { 338 assert.NoError(t, err) 339 } else { 340 assert.EqualError(t, err, test.expectedError) 341 } 342 }) 343 } 344} 345 346func TestLivePresent(t *testing.T) { 347 if !envTest.IsLiveTest() { 348 t.Skip("Skipping live test") 349 } 350 // TODO implement this test 351} 352 353func TestLiveCleanUp(t *testing.T) { 354 if !envTest.IsLiveTest() { 355 t.Skip("Skipping live test") 356 } 357 // TODO implement this test 358} 359