diff options
| -rw-r--r-- | nccli.go | 156 | ||||
| -rw-r--r-- | nccli_test.go | 814 |
2 files changed, 850 insertions, 120 deletions
@@ -1,4 +1,4 @@ -package main +package nccli import ( "bufio" @@ -15,7 +15,6 @@ import ( var ( verbose = false - apiUrl string ) // Based on: https://www.namecheap.com/support/api/methods/domains-dns/set-hosts/ @@ -43,12 +42,12 @@ type GetResponseObj struct { } type Host struct { - XMLName xml.Name `xml:"host"` - Type string `xml:"Type,attr"` - Name string `xml:"Name,attr"` - Address string `xml:"Address,attr"` - MXPref int `xml:"MXPref,attr"` - TTL int `xml:"TTL,attr"` + //XMLName xml.Name `xml:"host"` + Type string `xml:"Type,attr"` + Name string `xml:"Name,attr"` + Address string `xml:"Address,attr"` + MXPref int `xml:"MXPref,attr"` + TTL int `xml:"TTL,attr"` } // Helper functions // @@ -69,12 +68,12 @@ func getEnv(key string) (string, error) { // Load ENV vars into the apiUrl var. // Returns nil if config loaded successfully, error otherwise. -func loadConfig() error { +func loadConfig() (string, error) { keys := [5]string{"API_USER", "API_KEY", "ACCESS_IP", "DOMAIN_MAIN", "DOMAIN_END"} for _, key := range keys { _, err := getEnv(key) if err != nil { - return err + return "", err } } user, _ := getEnv("API_USER") @@ -83,14 +82,14 @@ func loadConfig() error { domainMain, _ := getEnv("DOMAIN_MAIN") domainEnd, _ := getEnv("DOMAIN_END") - apiUrl = "https://api.namecheap.com/xml.response?ApiUser=" + user + + apiUrl := "https://api.namecheap.com/xml.response?ApiUser=" + user + "&ApiKey=" + apiKey + "&UserName=" + user + "&ClientIp=" + accessIP + "&SLD=" + domainMain + "&TLD=" + domainEnd - return nil + return apiUrl, nil } // Print host recrods to stdout. @@ -114,9 +113,9 @@ func logRecords(hosts []Host) { // Args: endPoint - the api method to call. // extraParams (optional) - more params to add to the apiUrl. // Returns the response byte stream. -func makeReq(endPoint string, extraParams ...string) []byte { +func makeReq(baseUrl string, endPoint string, extraParams ...string) ([]byte, error) { - methodUrl := apiUrl + "&Command=namecheap.domains.dns." + endPoint + methodUrl := baseUrl + "&Command=namecheap.domains.dns." + endPoint for _, param := range extraParams { methodUrl += param } @@ -135,18 +134,18 @@ func makeReq(endPoint string, extraParams ...string) []byte { case "sethosts": response, err = http.Post(methodUrl, "application/xml", nil) default: - log.Fatalln("Error: Invalid request type specified.") + return nil, fmt.Errorf("Error: Invalid request type specified.") } if err != nil { - log.Fatalln(err) + return nil, err } responseData, err := ioutil.ReadAll(response.Body) if err != nil { - log.Fatalln(err) + return nil, err } - return responseData + return responseData, nil } // Converts user input strings into ints. Errors if input is wrong. @@ -156,21 +155,19 @@ func makeReq(endPoint string, extraParams ...string) []byte { func normalizeUserChoices(userChoicesStrs []string, maxVal int) ([]int, error) { var chosenInts []int for _, choice := range userChoicesStrs { - text := strings.TrimSpace(choice) + text := strings.TrimSpace(choice) if len(text) == 0 { continue } val, err := strconv.Atoi(text) if err != nil { - //log.Fatalln("Error: Bad user input detected. Please only enter numbers.") - return nil, fmt.Errorf("Error: Bad input detected: %s. Please only enter numbers.", text) + return nil, fmt.Errorf("Error: Bad input detected: %s. Please only enter numbers.", text) } // validate if (val >= 0) && (val < maxVal) { chosenInts = append(chosenInts, val) } else { - //log.Fatalln("Error: Bad user input detected. One of the numbers is not correct.") - return nil, fmt.Errorf("Error: Bad user input detected. The number %d is out of range.", val) + return nil, fmt.Errorf("Error: Bad user input detected. The number %d is out of range.", val) } } return chosenInts, nil @@ -193,24 +190,30 @@ func updateHostRecordSet(hostRecords []Host, newHost Host) { // Performs the API calls that updates Namecheap in any way, // and handles the resulting response validation. // Args: urlEncodedHosts - params consisting of DNS records. -func updateNamecheapRecords(urlEncodedHosts string) { +func updateNamecheapRecords(apiUrl string, urlEncodedHosts string) error { // make the api request to namecheap - responseData := makeReq("sethosts", urlEncodedHosts) + responseData, err := makeReq(apiUrl, "sethosts", urlEncodedHosts) + if err != nil { + return err + } var responseObj SetResponseObj xml.Unmarshal(responseData, &responseObj) // validate - if responseObj.Status != "OK" { - log.Fatalln(string(responseData)) - log.Fatalln("Error: Api request failed. Exiting.") + switch responseObj.Status { + case "OK": + case "ERROR": + return fmt.Errorf("Error: Api request failed.") + default: + return fmt.Errorf("Error while parsing XML response.") } if !responseObj.DNSResultObj.IsSuccess { - log.Fatalln(string(responseData)) - log.Fatalln("Error: Something went wrong updating DNS records for:", responseObj.DNSResultObj.Domain) + return fmt.Errorf("Error: Something went wrong updating DNS records for %s", responseObj.DNSResultObj.Domain) } if verbose { fmt.Println("Success updating DNS records for:", responseObj.DNSResultObj.Domain) } + return nil } // Build a string with all DNS records. @@ -270,17 +273,23 @@ func userInputHandler() []string { // Get all existing DNS records. // Returns a slice of Host corresponding to DNS records. -func getAllRecords() []Host { - responseData := makeReq("gethosts") +func getAllRecords(apiUrl string) ([]Host, error) { + responseData, err := makeReq(apiUrl, "gethosts") + if err != nil { + return nil, err + } var responseObj GetResponseObj xml.Unmarshal(responseData, &responseObj) // validate - if responseObj.Status != "OK" { - log.Fatalln(string(responseData)) - log.Fatalln("Error: Api request failed. Exiting.") + switch responseObj.Status { + case "OK": + return responseObj.Hosts, nil + case "ERROR": + return nil, fmt.Errorf("Error: Api request failed. Exiting.") + default: + return nil, fmt.Errorf("Error while parsing XML response.") } - return responseObj.Hosts } // Add a DNS record. @@ -289,9 +298,12 @@ func getAllRecords() []Host { // rAddress - the value of the DNS record. // rMXPref - the priority of the DNS record. // rTTL - the ttl of the DNS record. -func addRecord(rName string, rType string, rAddress string, rMXPref int, rTTL int) { +func addRecord(apiUrl string, rName string, rType string, rAddress string, rMXPref int, rTTL int) error { // we need to get the records before we can add to it. - var hostRecords = getAllRecords() + hostRecords, err := getAllRecords(apiUrl) + if err != nil { + return err + } if verbose { logRecords(hostRecords) } @@ -303,23 +315,29 @@ func addRecord(rName string, rType string, rAddress string, rMXPref int, rTTL in TTL: rTTL, }) urlEncodedHosts := urlEncodeHosts(hostRecords) - updateNamecheapRecords(urlEncodedHosts) + err = updateNamecheapRecords(apiUrl, urlEncodedHosts) + if err != nil { + return err + } + return nil } // Remove DNS record(s). // Will prompt for user input. -func removeRecord() { +func removeRecord(apiUrl string) error { // we need to get the records before we can remove one - var hostRecords = getAllRecords() + hostRecords, err := getAllRecords(apiUrl) + if err != nil { + return err + } logRecords(hostRecords) // get user input for which ones to delete userChoicesStrs := userInputHandler() userChoicesInts, err := normalizeUserChoices(userChoicesStrs, len(hostRecords)) - if err != nil { - log.Fatalln(err) - } - + if err != nil { + return err + } // build the desired set of host records. var newHostRecords []Host @@ -336,7 +354,11 @@ func removeRecord() { } } urlEncodedHosts := urlEncodeHosts(newHostRecords) - updateNamecheapRecords(urlEncodedHosts) + err = updateNamecheapRecords(apiUrl, urlEncodedHosts) + if err != nil { + return err + } + return nil } // Update DNS record. @@ -345,9 +367,12 @@ func removeRecord() { // rAddress - the value of the DNS record. // rMXPref - the priority of the DNS record. // rTTL - the ttl of the DNS record. -func updateRecord(rName string, rType string, rAddress string, rMXPref int, rTTL int) { +func updateRecord(apiUrl string, rName string, rType string, rAddress string, rMXPref int, rTTL int) error { // we need to get the records before we can update it. - var hostRecords = getAllRecords() + hostRecords, err := getAllRecords(apiUrl) + if err != nil { + return err + } if verbose { logRecords(hostRecords) } @@ -362,7 +387,11 @@ func updateRecord(rName string, rType string, rAddress string, rMXPref int, rTTL }, ) urlEncodedHosts := urlEncodeHosts(hostRecords) - updateNamecheapRecords(urlEncodedHosts) + err = updateNamecheapRecords(apiUrl, urlEncodedHosts) + if err != nil { + return err + } + return nil } func main() { @@ -398,38 +427,51 @@ func main() { switch os.Args[1] { case "list": listCmd.Parse(os.Args[2:]) - err := loadConfig() + apiUrl, err := loadConfig() if err != nil { log.Fatalln(err) } - logRecords(getAllRecords()) + hostRecords, err := getAllRecords(apiUrl) + if err != nil { + log.Fatalln(err) + } + logRecords(hostRecords) case "add": addCmd.Parse(os.Args[2:]) if *addName == "" || *addType == "" || *addAddress == "" { log.Fatalln(addUpdateErrorMsg) } - err := loadConfig() + apiUrl, err := loadConfig() + if err != nil { + log.Fatalln(err) + } + err = addRecord(apiUrl, *addName, *addType, *addAddress, *addMXPref, *addTTL) if err != nil { log.Fatalln(err) } - addRecord(*addName, *addType, *addAddress, *addMXPref, *addTTL) case "remove": removeCmd.Parse(os.Args[2:]) - err := loadConfig() + apiUrl, err := loadConfig() + if err != nil { + log.Fatalln(err) + } + err = removeRecord(apiUrl) if err != nil { log.Fatalln(err) } - removeRecord() case "update": updateCmd.Parse(os.Args[2:]) if *updateName == "" || *updateType == "" || *updateAddress == "" { log.Fatalln(addUpdateErrorMsg) } - err := loadConfig() + apiUrl, err := loadConfig() + if err != nil { + log.Fatalln(err) + } + err = updateRecord(apiUrl, *updateName, *updateType, *updateAddress, *updateMXPref, *updateTTL) if err != nil { log.Fatalln(err) } - updateRecord(*updateName, *updateType, *updateAddress, *updateMXPref, *updateTTL) default: log.Fatalln(subCmdErrorMsg) } diff --git a/nccli_test.go b/nccli_test.go index 43f0308..11c4910 100644 --- a/nccli_test.go +++ b/nccli_test.go @@ -1,10 +1,12 @@ -package main +package nccli import ( "fmt" + "net/http" + "net/http/httptest" "os" - "testing" "reflect" + "testing" ) // checking for a valid return value. @@ -36,7 +38,7 @@ func TestLoadConfigMissingKey(t *testing.T) { os.Setenv("DOMAIN_MAIN", "mysite") // NOTE: DOMAIN_END is missing. - err := loadConfig() + _, err := loadConfig() _, errWant := getEnv("DOMAIN_END") if err.Error() != errWant.Error() || err == nil { @@ -53,7 +55,7 @@ func TestLoadConfigAllKeys(t *testing.T) { os.Setenv("DOMAIN_MAIN", "mysite") os.Setenv("DOMAIN_END", "xyz") - loadConfig() + apiUrl, _ := loadConfig() want := "https://api.namecheap.com/xml.response?ApiUser=user&ApiKey=mykey&UserName=user&ClientIp=1.1.1.1&SLD=mysite&TLD=xyz" if apiUrl != want { t.Fatalf(`apiUrl = %s, want %s`, apiUrl, want) @@ -62,124 +64,810 @@ func TestLoadConfigAllKeys(t *testing.T) { // checking for a valid return value. func TestNormalizeUserChoicesCleanInput(t *testing.T) { - userChoicesStrs := []string{"0", "1", "6"} - maxVal := 7 - want := []int{0, 1, 6} + userChoicesStrs := []string{"0", "1", "6"} + maxVal := 7 + want := []int{0, 1, 6} have, err := normalizeUserChoices(userChoicesStrs, maxVal) - if !reflect.DeepEqual(have, want) || err != nil { + if !reflect.DeepEqual(have, want) || err != nil { t.Fatalf(`normalizeUserChoices(%v, %d) = %v, %v, want %v,nil`, userChoicesStrs, maxVal, have, err, want) - } + } } // checking for a valid return value. func TestNormalizeUserChoicesExtraSpaces(t *testing.T) { - userChoicesStrs := []string{" ", " ", "1", "", " 5 ", " "} - maxVal := 7 - want := []int{1, 5} + userChoicesStrs := []string{" ", " ", "1", "", " 5 ", " "} + maxVal := 7 + want := []int{1, 5} have, err := normalizeUserChoices(userChoicesStrs, maxVal) - if !reflect.DeepEqual(have, want) || err != nil { + if !reflect.DeepEqual(have, want) || err != nil { t.Fatalf(`normalizeUserChoices(%v, %d) = %v, %v, want %v,nil`, userChoicesStrs, maxVal, have, err, want) - } + } } // checking for an error. func TestNormalizeUserChoicesWrongChar(t *testing.T) { - userChoicesStrs := []string{"6", "5b"} - maxVal := 7 - errWant := "Error: Bad input detected: 5b. Please only enter numbers." + userChoicesStrs := []string{"6", "5b"} + maxVal := 7 + errWant := "Error: Bad input detected: 5b. Please only enter numbers." have, err := normalizeUserChoices(userChoicesStrs, maxVal) - if have != nil || err.Error() != errWant { + if have != nil || err.Error() != errWant { t.Fatalf(`normalizeUserChoices(%v, %d) = %v, %v, want nil,%v`, userChoicesStrs, maxVal, have, err, errWant) - } + } } // checking for an error. func TestNormalizeUserChoicesOutOfRangeLower(t *testing.T) { - userChoicesStrs := []string{"-1", "5"} - maxVal := 7 - errWant := "Error: Bad user input detected. The number -1 is out of range." + userChoicesStrs := []string{"-1", "5"} + maxVal := 7 + errWant := "Error: Bad user input detected. The number -1 is out of range." have, err := normalizeUserChoices(userChoicesStrs, maxVal) - if have != nil || err.Error() != errWant { + if have != nil || err.Error() != errWant { t.Fatalf(`normalizeUserChoices(%v, %d) = %v, %v, want nil,%v`, userChoicesStrs, maxVal, have, err, errWant) - } + } } // checking for an error. func TestNormalizeUserChoicesOutOfRangeUpper(t *testing.T) { - userChoicesStrs := []string{"0", "7"} - maxVal := 7 - errWant := "Error: Bad user input detected. The number 7 is out of range." + userChoicesStrs := []string{"0", "7"} + maxVal := 7 + errWant := "Error: Bad user input detected. The number 7 is out of range." have, err := normalizeUserChoices(userChoicesStrs, maxVal) - if have != nil || err.Error() != errWant { + if have != nil || err.Error() != errWant { t.Fatalf(`normalizeUserChoices(%v, %d) = %v, %v, want nil,%v`, userChoicesStrs, maxVal, have, err, errWant) - } + } } // checking for a valid return value. func TestUpdateHostRecordSet(t *testing.T) { - rec1 := Host{ + rec1 := Host{ Name: "@", Type: "T", Address: "foo", MXPref: 1, TTL: 1, - } - rec2 := Host{ + } + rec2 := Host{ Name: "www", Type: "T", Address: "bar", MXPref: 1, TTL: 1, - } - updatedRec2 := Host{ - Name: "www", - Type: "T", - Address: "new", - MXPref: 10, - TTL: 99, - } - recs := []Host{rec1, rec2} - want := []Host{rec1, updatedRec2} - // update recs in place + } + updatedRec2 := Host{ + Name: "www", + Type: "T", + Address: "new", + MXPref: 10, + TTL: 99, + } + recs := []Host{rec1, rec2} + want := []Host{rec1, updatedRec2} + // update recs in place updateHostRecordSet(recs, updatedRec2) - if !reflect.DeepEqual(recs, want) { - t.Fatalf(`have %v, want %v`, recs, want) - } + if !reflect.DeepEqual(recs, want) { + t.Fatalf(`have %v, want %v`, recs, want) + } } // checking for a valid return value. func TestUpdateHostRecordSetNoOp(t *testing.T) { - rec1 := Host{ + rec1 := Host{ Name: "@", Type: "T", Address: "foo", MXPref: 1, TTL: 1, - } - rec2 := Host{ + } + rec2 := Host{ Name: "www", Type: "T", Address: "bar", MXPref: 1, TTL: 1, - } - updatedRec2 := Host{ - Name: "www", - Type: "A", - Address: "new", - MXPref: 10, - TTL: 99, - } - recs := []Host{rec1, rec2} - want := []Host{rec1, rec2} - // update recs in place + } + updatedRec2 := Host{ + Name: "www", + Type: "A", + Address: "new", + MXPref: 10, + TTL: 99, + } + recs := []Host{rec1, rec2} + want := []Host{rec1, rec2} + // update recs in place updateHostRecordSet(recs, updatedRec2) - if !reflect.DeepEqual(recs, want) { - t.Fatalf(`have %v, want %v`, recs, want) - } + if !reflect.DeepEqual(recs, want) { + t.Fatalf(`have %v, want %v`, recs, want) + } +} + +func TestUrlEncodeHosts(t *testing.T) { + rec1 := Host{ + Name: "@", + Type: "T", + Address: "foo", + MXPref: 1, + TTL: 1, + } + rec2 := Host{ + Name: "www", + Type: "T", + Address: "bar", + MXPref: 1, + TTL: 42, + } + rec3 := Host{ + Name: "www", + Type: "A", + Address: "biz", + MXPref: 10, + TTL: 19, + } + recs := []Host{rec1, rec2, rec3} + have := urlEncodeHosts(recs) + want := fmt.Sprintf("&HostName1=%s&RecordType1=%s&Address1=%s&MXPref1=%d&TTL1=%d", + rec1.Name, + rec1.Type, + rec1.Address, + rec1.MXPref, + rec1.TTL, + ) + want += fmt.Sprintf("&HostName2=%s&RecordType2=%s&Address2=%s&MXPref2=%d&TTL2=%d", + rec2.Name, + rec2.Type, + rec2.Address, + rec2.MXPref, + rec2.TTL, + ) + want += fmt.Sprintf("&HostName3=%s&RecordType3=%s&Address3=%s&MXPref3=%d&TTL3=%d", + rec3.Name, + rec3.Type, + rec3.Address, + rec3.MXPref, + rec3.TTL, + ) + if have != want { + t.Fatalf(`have %s, want %s`, have, want) + } +} + +func TestMakeReqGetHosts(t *testing.T) { + mockBaseUrl := "/xml.response?ApiUser=" + "myuser" + + "&ApiKey=" + "mykey" + + "&UserName=" + "myuser" + + "&ClientIp=" + "1.1.1.1" + + "&SLD=" + "mysite" + + "&TLD=" + "xyz" + + wantUri := mockBaseUrl + "&Command=namecheap.domains.dns." + "gethosts" + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.RequestURI != wantUri { + t.Fatalf("have %s, want %s", r.RequestURI, wantUri) + } + })) + defer server.Close() + makeReq(server.URL+mockBaseUrl, "gethosts") +} + +func TestMakeReqSetHosts(t *testing.T) { + mockBaseUrl := "/xml.response?ApiUser=" + "myuser" + + "&ApiKey=" + "mykey" + + "&UserName=" + "myuser" + + "&ClientIp=" + "1.1.1.1" + + "&SLD=" + "mysite" + + "&TLD=" + "xyz" + + wantUri := mockBaseUrl + "&Command=namecheap.domains.dns." + "sethosts" + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.RequestURI != wantUri { + t.Fatalf("have %s, want %s", r.RequestURI, wantUri) + } + })) + defer server.Close() + makeReq(server.URL+mockBaseUrl, "sethosts") +} + +func TestMakeReqWrongEndpoint(t *testing.T) { + _, have := makeReq("someurl", "foobar") + want := "Error: Invalid request type specified." + + if have.Error() != want { + t.Fatalf("have %s, want: %s", have, want) + } +} + +func TestGetAllRecordsValidXMLResponse(t *testing.T) { + + mockBaseUrl := "/xml.response?ApiUser=" + "myuser" + + "&ApiKey=" + "mykey" + + "&UserName=" + "myuser" + + "&ClientIp=" + "1.1.1.1" + + "&SLD=" + "mysite" + + "&TLD=" + "xyz" + + xmlRes := + ` + <?xml version="1.0" encoding="UTF-8"?> + <ApiResponse xmlns="http://api.namecheap.com/xml.response" Status="OK"> + <Errors /> + <RequestedCommand>namecheap.domains.dns.getHosts</RequestedCommand> + <CommandResponse Type="namecheap.domains.dns.getHosts"> + <DomainDNSGetHostsResult Domain="domain.com" IsUsingOurDNS="true"> + <host HostId="12" Name="@" Type="A" Address="1.2.3.4" MXPref="10" TTL="1800" /> + <host HostId="14" Name="www" Type="A" Address="122.23.3.7" MXPref="10" TTL="1800" /> + </DomainDNSGetHostsResult> + </CommandResponse> + <Server>SERVER-NAME</Server> + <GMTTimeDifference>+5</GMTTimeDifference> + <ExecutionTime>32.76</ExecutionTime> + </ApiResponse> + ` + want := []Host{Host{ + Name: "@", + Type: "A", + Address: "1.2.3.4", + MXPref: 10, + TTL: 1800, + }, Host{ + Name: "www", + Type: "A", + Address: "122.23.3.7", + MXPref: 10, + TTL: 1800, + }} + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(xmlRes)) + })) + defer server.Close() + have, err := getAllRecords(server.URL + mockBaseUrl) + if !reflect.DeepEqual(have, want) || err != nil { + t.Fatalf("have %v, %v, want %v, nil", have, err, want) + } +} + +func TestGetAllRecordsValidXMLResponseNoHosts(t *testing.T) { + + mockBaseUrl := "/xml.response?ApiUser=" + "myuser" + + "&ApiKey=" + "mykey" + + "&UserName=" + "myuser" + + "&ClientIp=" + "1.1.1.1" + + "&SLD=" + "mysite" + + "&TLD=" + "xyz" + + xmlRes := + ` + <?xml version="1.0" encoding="UTF-8"?> + <ApiResponse xmlns="http://api.namecheap.com/xml.response" Status="OK"> + <Errors /> + <RequestedCommand>namecheap.domains.dns.getHosts</RequestedCommand> + <CommandResponse Type="namecheap.domains.dns.getHosts"> + <DomainDNSGetHostsResult Domain="domain.com" IsUsingOurDNS="true"> + </DomainDNSGetHostsResult> + </CommandResponse> + <Server>SERVER-NAME</Server> + <GMTTimeDifference>+5</GMTTimeDifference> + <ExecutionTime>32.76</ExecutionTime> + </ApiResponse> + ` + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(xmlRes)) + })) + defer server.Close() + have, err := getAllRecords(server.URL + mockBaseUrl) + if have != nil || err != nil { + t.Fatalf("have %v, %v, want nil, nil", have, err) + } +} + +func TestGetAllRecordsErrorXMLResponse(t *testing.T) { + + mockBaseUrl := "/xml.response?ApiUser=" + "myuser" + + "&ApiKey=" + "mykey" + + "&UserName=" + "myuser" + + "&ClientIp=" + "1.1.1.1" + + "&SLD=" + "mysite" + + "&TLD=" + "xyz" + + xmlRes := + ` + <?xml version="1.0" encoding="UTF-8"?> + <ApiResponse Status="ERROR"> + <Errors> + <Error Number="0">Error message</Error> + </Errors> + </ApiResponse> + ` + want := "Error: Api request failed. Exiting." + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(xmlRes)) + })) + defer server.Close() + have, err := getAllRecords(server.URL + mockBaseUrl) + if have != nil || err.Error() != want { + t.Fatalf("have %s, want %s", err, want) + } +} + +func TestGetAllRecordsBadXMLResponse(t *testing.T) { + + mockBaseUrl := "/xml.response?ApiUser=" + "myuser" + + "&ApiKey=" + "mykey" + + "&UserName=" + "myuser" + + "&ClientIp=" + "1.1.1.1" + + "&SLD=" + "mysite" + + "&TLD=" + "xyz" + + xmlRes := "<>" + want := "Error while parsing XML response." + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(xmlRes)) + })) + defer server.Close() + have, err := getAllRecords(server.URL + mockBaseUrl) + if have != nil || err.Error() != want { + t.Fatalf("have %s, want %s", err, want) + } +} + +func TestAddRecordValidXMLResponse(t *testing.T) { + + mockBaseUrl := "/xml.response?ApiUser=" + "myuser" + + "&ApiKey=" + "mykey" + + "&UserName=" + "myuser" + + "&ClientIp=" + "1.1.1.1" + + "&SLD=" + "mysite" + + "&TLD=" + "xyz" + + getUri := mockBaseUrl + "&Command=namecheap.domains.dns." + "gethosts" + setUri := mockBaseUrl + "&Command=namecheap.domains.dns." + "sethosts" + + xmlGetRes := + ` + <?xml version="1.0" encoding="UTF-8"?> + <ApiResponse xmlns="http://api.namecheap.com/xml.response" Status="OK"> + <Errors /> + <RequestedCommand>namecheap.domains.dns.getHosts</RequestedCommand> + <CommandResponse Type="namecheap.domains.dns.getHosts"> + <DomainDNSGetHostsResult Domain="domain.com" IsUsingOurDNS="true"> + <host HostId="12" Name="@" Type="A" Address="1.2.3.4" MXPref="10" TTL="1800" /> + <host HostId="14" Name="www" Type="A" Address="122.23.3.7" MXPref="10" TTL="1800" /> + </DomainDNSGetHostsResult> + </CommandResponse> + <Server>SERVER-NAME</Server> + <GMTTimeDifference>+5</GMTTimeDifference> + <ExecutionTime>32.76</ExecutionTime> + </ApiResponse> + ` + + xmlSetRes := + ` + <?xml version="1.0" encoding="UTF-8"?> + <ApiResponse xmlns="https://api.namecheap.com/xml.response" Status="OK"> + <Errors /> + <RequestedCommand>namecheap.domains.dns.setHosts</RequestedCommand> + <CommandResponse Type="namecheap.domains.dns.setHosts"> + <DomainDNSSetHostsResult Domain="domain51.com" IsSuccess="true" /> + </CommandResponse> + <Server>SERVER-NAME</Server> + <GMTTimeDifference>+5</GMTTimeDifference> + <ExecutionTime>32.76</ExecutionTime> + </ApiResponse> + ` + newHost := Host{ + Name: "all", + Type: "TXT", + Address: "foobar", + MXPref: 42, + TTL: 4242, + } + newHosts := []Host{Host{ + Name: "@", + Type: "A", + Address: "1.2.3.4", + MXPref: 10, + TTL: 1800, + }, Host{ + Name: "www", + Type: "A", + Address: "122.23.3.7", + MXPref: 10, + TTL: 1800, + }, newHost} + setUri += urlEncodeHosts(newHosts) + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.RequestURI { + case getUri: + w.Write([]byte(xmlGetRes)) + case setUri: + w.Write([]byte(xmlSetRes)) + } + })) + defer server.Close() + err := addRecord(server.URL+mockBaseUrl, newHost.Name, newHost.Type, newHost.Address, newHost.MXPref, newHost.TTL) + if err != nil { + t.Fatalf("have %v, want nil", err) + } +} + +func TestAddRecordInValidXMLResponse(t *testing.T) { + + mockBaseUrl := "/xml.response?ApiUser=" + "myuser" + + "&ApiKey=" + "mykey" + + "&UserName=" + "myuser" + + "&ClientIp=" + "1.1.1.1" + + "&SLD=" + "mysite" + + "&TLD=" + "xyz" + + getUri := mockBaseUrl + "&Command=namecheap.domains.dns." + "gethosts" + setUri := mockBaseUrl + "&Command=namecheap.domains.dns." + "sethosts" + + xmlGetRes := + ` + <?xml version="1.0" encoding="UTF-8"?> + <ApiResponse xmlns="http://api.namecheap.com/xml.response" Status="OK"> + <Errors /> + <RequestedCommand>namecheap.domains.dns.getHosts</RequestedCommand> + <CommandResponse Type="namecheap.domains.dns.getHosts"> + <DomainDNSGetHostsResult Domain="domain.com" IsUsingOurDNS="true"> + <host HostId="12" Name="@" Type="A" Address="1.2.3.4" MXPref="10" TTL="1800" /> + <host HostId="14" Name="www" Type="A" Address="122.23.3.7" MXPref="10" TTL="1800" /> + </DomainDNSGetHostsResult> + </CommandResponse> + <Server>SERVER-NAME</Server> + <GMTTimeDifference>+5</GMTTimeDifference> + <ExecutionTime>32.76</ExecutionTime> + </ApiResponse> + ` + + xmlSetRes := `<>` + newHost := Host{ + Name: "all", + Type: "TXT", + Address: "foobar", + MXPref: 42, + TTL: 4242, + } + newHosts := []Host{Host{ + Name: "@", + Type: "A", + Address: "1.2.3.4", + MXPref: 10, + TTL: 1800, + }, Host{ + Name: "www", + Type: "A", + Address: "122.23.3.7", + MXPref: 10, + TTL: 1800, + }, newHost} + setUri += urlEncodeHosts(newHosts) + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.RequestURI { + case getUri: + w.Write([]byte(xmlGetRes)) + case setUri: + w.Write([]byte(xmlSetRes)) + } + })) + defer server.Close() + errHave := addRecord(server.URL+mockBaseUrl, newHost.Name, newHost.Type, newHost.Address, newHost.MXPref, newHost.TTL) + errWant := "Error while parsing XML response." + if errHave.Error() != errWant { + t.Fatalf("have %v, want %s", errHave, errWant) + } +} + +func TestAddRecordErrorXMLResponse(t *testing.T) { + + mockBaseUrl := "/xml.response?ApiUser=" + "myuser" + + "&ApiKey=" + "mykey" + + "&UserName=" + "myuser" + + "&ClientIp=" + "1.1.1.1" + + "&SLD=" + "mysite" + + "&TLD=" + "xyz" + + getUri := mockBaseUrl + "&Command=namecheap.domains.dns." + "gethosts" + setUri := mockBaseUrl + "&Command=namecheap.domains.dns." + "sethosts" + + xmlGetRes := + ` + <?xml version="1.0" encoding="UTF-8"?> + <ApiResponse xmlns="http://api.namecheap.com/xml.response" Status="OK"> + <Errors /> + <RequestedCommand>namecheap.domains.dns.getHosts</RequestedCommand> + <CommandResponse Type="namecheap.domains.dns.getHosts"> + <DomainDNSGetHostsResult Domain="domain.com" IsUsingOurDNS="true"> + <host HostId="12" Name="@" Type="A" Address="1.2.3.4" MXPref="10" TTL="1800" /> + <host HostId="14" Name="www" Type="A" Address="122.23.3.7" MXPref="10" TTL="1800" /> + </DomainDNSGetHostsResult> + </CommandResponse> + <Server>SERVER-NAME</Server> + <GMTTimeDifference>+5</GMTTimeDifference> + <ExecutionTime>32.76</ExecutionTime> + </ApiResponse> + ` + xmlSetRes := + ` + <?xml version="1.0" encoding="UTF-8"?> + <ApiResponse Status="ERROR"> + <Errors> + <Error Number="0">Error message</Error> + </Errors> + </ApiResponse> + ` + newHost := Host{ + Name: "all", + Type: "TXT", + Address: "foobar", + MXPref: 42, + TTL: 4242, + } + newHosts := []Host{Host{ + Name: "@", + Type: "A", + Address: "1.2.3.4", + MXPref: 10, + TTL: 1800, + }, Host{ + Name: "www", + Type: "A", + Address: "122.23.3.7", + MXPref: 10, + TTL: 1800, + }, newHost} + setUri += urlEncodeHosts(newHosts) + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.RequestURI { + case getUri: + w.Write([]byte(xmlGetRes)) + case setUri: + w.Write([]byte(xmlSetRes)) + } + })) + defer server.Close() + errHave := addRecord(server.URL+mockBaseUrl, newHost.Name, newHost.Type, newHost.Address, newHost.MXPref, newHost.TTL) + errWant := "Error: Api request failed." + if errHave.Error() != errWant { + t.Fatalf("have %v, want %s", errHave, errWant) + } +} + +func TestUpdateRecordValidXMLResponse(t *testing.T) { + + mockBaseUrl := "/xml.response?ApiUser=" + "myuser" + + "&ApiKey=" + "mykey" + + "&UserName=" + "myuser" + + "&ClientIp=" + "1.1.1.1" + + "&SLD=" + "mysite" + + "&TLD=" + "xyz" + + getUri := mockBaseUrl + "&Command=namecheap.domains.dns." + "gethosts" + setUri := mockBaseUrl + "&Command=namecheap.domains.dns." + "sethosts" + + xmlGetRes := + ` + <?xml version="1.0" encoding="UTF-8"?> + <ApiResponse xmlns="http://api.namecheap.com/xml.response" Status="OK"> + <Errors /> + <RequestedCommand>namecheap.domains.dns.getHosts</RequestedCommand> + <CommandResponse Type="namecheap.domains.dns.getHosts"> + <DomainDNSGetHostsResult Domain="domain.com" IsUsingOurDNS="true"> + <host HostId="12" Name="@" Type="A" Address="1.2.3.4" MXPref="10" TTL="1800" /> + <host HostId="14" Name="www" Type="A" Address="122.23.3.7" MXPref="10" TTL="1800" /> + </DomainDNSGetHostsResult> + </CommandResponse> + <Server>SERVER-NAME</Server> + <GMTTimeDifference>+5</GMTTimeDifference> + <ExecutionTime>32.76</ExecutionTime> + </ApiResponse> + ` + xmlSetRes := + ` + <?xml version="1.0" encoding="UTF-8"?> + <ApiResponse xmlns="https://api.namecheap.com/xml.response" Status="OK"> + <Errors /> + <RequestedCommand>namecheap.domains.dns.setHosts</RequestedCommand> + <CommandResponse Type="namecheap.domains.dns.setHosts"> + <DomainDNSSetHostsResult Domain="domain51.com" IsSuccess="true" /> + </CommandResponse> + <Server>SERVER-NAME</Server> + <GMTTimeDifference>+5</GMTTimeDifference> + <ExecutionTime>32.76</ExecutionTime> + </ApiResponse> + ` + newHost := Host{ + Name: "www", + Type: "A", + Address: "foobar", + MXPref: 42, + TTL: 4242, + } + newHosts := []Host{Host{ + Name: "@", + Type: "A", + Address: "1.2.3.4", + MXPref: 10, + TTL: 1800, + }, Host{ + Name: "www", + Type: "A", + Address: "122.23.3.7", + MXPref: 10, + TTL: 1800, + }} + updateHostRecordSet(newHosts, newHost) + setUri += urlEncodeHosts(newHosts) + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.RequestURI { + case getUri: + w.Write([]byte(xmlGetRes)) + case setUri: + w.Write([]byte(xmlSetRes)) + } + })) + defer server.Close() + err := updateRecord(server.URL+mockBaseUrl, newHost.Name, newHost.Type, newHost.Address, newHost.MXPref, newHost.TTL) + if err != nil { + t.Fatalf("have %v, want nil", err) + } +} + +func TestUpdateRecordInValidXMLResponse(t *testing.T) { + + mockBaseUrl := "/xml.response?ApiUser=" + "myuser" + + "&ApiKey=" + "mykey" + + "&UserName=" + "myuser" + + "&ClientIp=" + "1.1.1.1" + + "&SLD=" + "mysite" + + "&TLD=" + "xyz" + + getUri := mockBaseUrl + "&Command=namecheap.domains.dns." + "gethosts" + setUri := mockBaseUrl + "&Command=namecheap.domains.dns." + "sethosts" + + xmlGetRes := + ` + <?xml version="1.0" encoding="UTF-8"?> + <ApiResponse xmlns="http://api.namecheap.com/xml.response" Status="OK"> + <Errors /> + <RequestedCommand>namecheap.domains.dns.getHosts</RequestedCommand> + <CommandResponse Type="namecheap.domains.dns.getHosts"> + <DomainDNSGetHostsResult Domain="domain.com" IsUsingOurDNS="true"> + <host HostId="12" Name="@" Type="A" Address="1.2.3.4" MXPref="10" TTL="1800" /> + <host HostId="14" Name="www" Type="A" Address="122.23.3.7" MXPref="10" TTL="1800" /> + </DomainDNSGetHostsResult> + </CommandResponse> + <Server>SERVER-NAME</Server> + <GMTTimeDifference>+5</GMTTimeDifference> + <ExecutionTime>32.76</ExecutionTime> + </ApiResponse> + ` + + xmlSetRes := `<>` + newHost := Host{ + Name: "www", + Type: "A", + Address: "foobar", + MXPref: 42, + TTL: 4242, + } + newHosts := []Host{Host{ + Name: "@", + Type: "A", + Address: "1.2.3.4", + MXPref: 10, + TTL: 1800, + }, Host{ + Name: "www", + Type: "A", + Address: "122.23.3.7", + MXPref: 10, + TTL: 1800, + }} + updateHostRecordSet(newHosts, newHost) + setUri += urlEncodeHosts(newHosts) + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.RequestURI { + case getUri: + w.Write([]byte(xmlGetRes)) + case setUri: + w.Write([]byte(xmlSetRes)) + } + })) + defer server.Close() + errHave := updateRecord(server.URL+mockBaseUrl, newHost.Name, newHost.Type, newHost.Address, newHost.MXPref, newHost.TTL) + errWant := "Error while parsing XML response." + if errHave.Error() != errWant { + t.Fatalf("have %v, want %s", errHave, errWant) + } +} + +func TestUpdateRecordErrorXMLResponse(t *testing.T) { + + mockBaseUrl := "/xml.response?ApiUser=" + "myuser" + + "&ApiKey=" + "mykey" + + "&UserName=" + "myuser" + + "&ClientIp=" + "1.1.1.1" + + "&SLD=" + "mysite" + + "&TLD=" + "xyz" + + getUri := mockBaseUrl + "&Command=namecheap.domains.dns." + "gethosts" + setUri := mockBaseUrl + "&Command=namecheap.domains.dns." + "sethosts" + + xmlGetRes := + ` + <?xml version="1.0" encoding="UTF-8"?> + <ApiResponse xmlns="http://api.namecheap.com/xml.response" Status="OK"> + <Errors /> + <RequestedCommand>namecheap.domains.dns.getHosts</RequestedCommand> + <CommandResponse Type="namecheap.domains.dns.getHosts"> + <DomainDNSGetHostsResult Domain="domain.com" IsUsingOurDNS="true"> + <host HostId="12" Name="@" Type="A" Address="1.2.3.4" MXPref="10" TTL="1800" /> + <host HostId="14" Name="www" Type="A" Address="122.23.3.7" MXPref="10" TTL="1800" /> + </DomainDNSGetHostsResult> + </CommandResponse> + <Server>SERVER-NAME</Server> + <GMTTimeDifference>+5</GMTTimeDifference> + <ExecutionTime>32.76</ExecutionTime> + </ApiResponse> + ` + xmlSetRes := + ` + <?xml version="1.0" encoding="UTF-8"?> + <ApiResponse Status="ERROR"> + <Errors> + <Error Number="0">Error message</Error> + </Errors> + </ApiResponse> + ` + newHost := Host{ + Name: "www", + Type: "A", + Address: "foobar", + MXPref: 42, + TTL: 4242, + } + newHosts := []Host{Host{ + Name: "@", + Type: "A", + Address: "1.2.3.4", + MXPref: 10, + TTL: 1800, + }, Host{ + Name: "www", + Type: "A", + Address: "122.23.3.7", + MXPref: 10, + TTL: 1800, + }} + updateHostRecordSet(newHosts, newHost) + setUri += urlEncodeHosts(newHosts) + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.RequestURI { + case getUri: + w.Write([]byte(xmlGetRes)) + case setUri: + w.Write([]byte(xmlSetRes)) + } + })) + defer server.Close() + errHave := updateRecord(server.URL+mockBaseUrl, newHost.Name, newHost.Type, newHost.Address, newHost.MXPref, newHost.TTL) + errWant := "Error: Api request failed." + if errHave.Error() != errWant { + t.Fatalf("have %v, want %s", errHave, errWant) + } } |
