From a9aa040430cf00c5337c5d2b8caf0fe1a1d0c84d Mon Sep 17 00:00:00 2001 From: Ferdinando Formica Date: Fri, 31 May 2024 11:07:09 +0100 Subject: [PATCH] Add new redirect objects (#226) * PENG-3135: add redirect, fixes * Change not found handling * PENG-3135: Update object definitions * Fix cert pagination, add links to docs * Use goimports in place of the old golint --- CHANGELOG.md | 6 + Makefile | 2 +- mockns1/application.go | 3 +- mockns1/redirect.go | 67 +++++++ mockns1/redirect_certificate.go | 67 +++++++ mockns1/version.go | 3 +- rest/_examples/applications.go | 3 +- rest/_examples/datasets.go | 3 +- rest/client.go | 71 +++---- rest/dataset_test.go | 5 +- rest/model/dataset/dataset_test.go | 5 +- rest/model/dns/record_test.go | 4 +- rest/model/pulsar/application_test.go | 3 +- rest/model/redirect/certificate.go | 30 +++ rest/model/redirect/certificate_test.go | 65 +++++++ rest/model/redirect/configuration.go | 128 ++++++++++++ rest/model/redirect/configuration_test.go | 123 ++++++++++++ rest/optiondef.go | 3 +- rest/optiondef_test.go | 3 +- rest/redirect.go | 179 +++++++++++++++++ rest/redirect_certificate.go | 175 +++++++++++++++++ rest/redirect_certificate_test.go | 226 ++++++++++++++++++++++ rest/redirect_test.go | 223 +++++++++++++++++++++ rest/reservation_test.go | 3 +- rest/scope_test.go | 3 +- rest/scopegroup.go | 3 +- rest/scopegroup_test.go | 3 +- rest/version.go | 3 +- rest/version_test.go | 5 +- 29 files changed, 1362 insertions(+), 55 deletions(-) create mode 100644 mockns1/redirect.go create mode 100644 mockns1/redirect_certificate.go create mode 100644 rest/model/redirect/certificate.go create mode 100644 rest/model/redirect/certificate_test.go create mode 100644 rest/model/redirect/configuration.go create mode 100644 rest/model/redirect/configuration_test.go create mode 100644 rest/redirect.go create mode 100644 rest/redirect_certificate.go create mode 100644 rest/redirect_certificate_test.go create mode 100644 rest/redirect_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 7cfd106..d232a27 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## 2.11.0 (May 23rd, 2024) + +FEATURES: + +* Adds support for creating and modifying HTTP/HTTPS redirects + ## 2.10.0 (April 18th, 2024) FEATURES: diff --git a/Makefile b/Makefile index 0932ae7..ad23c94 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ fmt: test: go fmt ./... go vet ./... - golint ./... + goimports -w . go test ./... .PHONY: all fmt test diff --git a/mockns1/application.go b/mockns1/application.go index a33d77c..13ac29a 100644 --- a/mockns1/application.go +++ b/mockns1/application.go @@ -1,8 +1,9 @@ package mockns1 import ( - "gopkg.in/ns1/ns1-go.v2/rest/model/pulsar" "net/http" + + "gopkg.in/ns1/ns1-go.v2/rest/model/pulsar" ) // AddApplicationTestCase sets up a test case for the api.Client.application.List() diff --git a/mockns1/redirect.go b/mockns1/redirect.go new file mode 100644 index 0000000..9925759 --- /dev/null +++ b/mockns1/redirect.go @@ -0,0 +1,67 @@ +package mockns1 + +import ( + "net/http" + + "gopkg.in/ns1/ns1-go.v2/rest/model/redirect" +) + +// AddRedirectListTestCase sets up a test case for the api.Client.Redirects.List() +// function +func (s *Service) AddRedirectListTestCase( + requestHeaders, responseHeaders http.Header, + response *redirect.ConfigurationList, +) error { + return s.AddTestCase( + http.MethodGet, "/redirect", http.StatusOK, requestHeaders, + responseHeaders, "", response, + ) +} + +// AddRedirectGetTestCase sets up a test case for the api.Client.Redirects.Get() +// function +func (s *Service) AddRedirectGetTestCase( + id string, + requestHeaders, responseHeaders http.Header, + response *redirect.Configuration, +) error { + return s.AddTestCase( + http.MethodGet, "/redirect/"+id, http.StatusOK, requestHeaders, + responseHeaders, "", response, + ) +} + +// AddRedirectCreateTestCase sets up a test case for the api.Client.Redirects.Create() +// function +func (s *Service) AddRedirectCreateTestCase( + requestHeaders, responseHeaders http.Header, + request, response *redirect.Configuration, +) error { + return s.AddTestCase( + http.MethodPut, "/redirect", http.StatusCreated, requestHeaders, + responseHeaders, request, response, + ) +} + +// AddRedirectUpdateTestCase sets up a test case for the api.Client.Redirects.Update() +// function +func (s *Service) AddRedirectUpdateTestCase( + requestHeaders, responseHeaders http.Header, + request, response *redirect.Configuration, +) error { + return s.AddTestCase( + http.MethodPost, "/redirect/"+*request.ID, http.StatusOK, requestHeaders, + responseHeaders, request, response, + ) +} + +// AddRedirectDeleteTestCase sets up a test case for the api.Client.Redirects.Delete() +// function +func (s *Service) AddRedirectDeleteTestCase( + id string, requestHeaders, responseHeaders http.Header, +) error { + return s.AddTestCase( + http.MethodDelete, "/redirect/"+id, http.StatusNoContent, requestHeaders, + responseHeaders, "", "", + ) +} diff --git a/mockns1/redirect_certificate.go b/mockns1/redirect_certificate.go new file mode 100644 index 0000000..6d902d6 --- /dev/null +++ b/mockns1/redirect_certificate.go @@ -0,0 +1,67 @@ +package mockns1 + +import ( + "net/http" + + "gopkg.in/ns1/ns1-go.v2/rest/model/redirect" +) + +// AddRedirectCertificateListTestCase sets up a test case for the api.Client.RedirectCertificates.List() +// function +func (s *Service) AddRedirectCertificateListTestCase( + requestHeaders, responseHeaders http.Header, + response *redirect.CertificateList, +) error { + return s.AddTestCase( + http.MethodGet, "/redirect/certificates", http.StatusOK, requestHeaders, + responseHeaders, "", response, + ) +} + +// AddRedirectCertificateGetTestCase sets up a test case for the api.Client.RedirectCertificates.Get() +// function +func (s *Service) AddRedirectCertificateGetTestCase( + id string, + requestHeaders, responseHeaders http.Header, + response *redirect.Certificate, +) error { + return s.AddTestCase( + http.MethodGet, "/redirect/certificates/"+id, http.StatusOK, requestHeaders, + responseHeaders, "", response, + ) +} + +// AddRedirectCertificateCreateTestCase sets up a test case for the api.Client.RedirectCertificates.Create() +// function +func (s *Service) AddRedirectCertificateCreateTestCase( + requestHeaders, responseHeaders http.Header, + request, response *redirect.Certificate, +) error { + return s.AddTestCase( + http.MethodPut, "/redirect/certificates", http.StatusCreated, requestHeaders, + responseHeaders, request, response, + ) +} + +// AddRedirectCertificateUpdateTestCase sets up a test case for the api.Client.RedirectCertificates.Update() +// function +func (s *Service) AddRedirectCertificateUpdateTestCase( + requestHeaders, responseHeaders http.Header, + id string, response *redirect.Certificate, +) error { + return s.AddTestCase( + http.MethodPost, "/redirect/certificates/"+id, http.StatusOK, requestHeaders, + responseHeaders, "", response, + ) +} + +// AddRedirectCertificateDeleteTestCase sets up a test case for the api.Client.RedirectCertificates.Delete() +// function +func (s *Service) AddRedirectCertificateDeleteTestCase( + id string, requestHeaders, responseHeaders http.Header, +) error { + return s.AddTestCase( + http.MethodDelete, "/redirect/certificates/"+id, http.StatusNoContent, requestHeaders, + responseHeaders, "", "", + ) +} diff --git a/mockns1/version.go b/mockns1/version.go index d01bfcb..0ebd5b1 100644 --- a/mockns1/version.go +++ b/mockns1/version.go @@ -2,8 +2,9 @@ package mockns1 import ( "fmt" - "gopkg.in/ns1/ns1-go.v2/rest/model/dns" "net/http" + + "gopkg.in/ns1/ns1-go.v2/rest/model/dns" ) // AddZoneListTestCase sets up a test case for the api.Client.Versions.List() diff --git a/rest/_examples/applications.go b/rest/_examples/applications.go index 2c6d4a9..46df9e2 100644 --- a/rest/_examples/applications.go +++ b/rest/_examples/applications.go @@ -2,13 +2,14 @@ package main import ( "fmt" - "gopkg.in/ns1/ns1-go.v2/rest/model/pulsar" "log" "net/http" "os" "strconv" "time" + "gopkg.in/ns1/ns1-go.v2/rest/model/pulsar" + api "gopkg.in/ns1/ns1-go.v2/rest" ) diff --git a/rest/_examples/datasets.go b/rest/_examples/datasets.go index 6e20280..dcd6190 100644 --- a/rest/_examples/datasets.go +++ b/rest/_examples/datasets.go @@ -3,7 +3,6 @@ package main import ( "encoding/json" "fmt" - "gopkg.in/ns1/ns1-go.v2/rest/model/dataset" "io" "log" "net/http" @@ -11,6 +10,8 @@ import ( "strings" "time" + "gopkg.in/ns1/ns1-go.v2/rest/model/dataset" + api "gopkg.in/ns1/ns1-go.v2/rest" ) diff --git a/rest/client.go b/rest/client.go index 945084a..0edf123 100644 --- a/rest/client.go +++ b/rest/client.go @@ -5,7 +5,6 @@ import ( "encoding/json" "fmt" "io" - "io/ioutil" "net/http" "net/url" "regexp" @@ -14,7 +13,7 @@ import ( ) const ( - clientVersion = "2.10.0" + clientVersion = "2.11.0" defaultEndpoint = "https://api.nsone.net/v1/" defaultShouldFollowPagination = true @@ -62,36 +61,38 @@ type Client struct { common service // Reuse a single struct instead of allocating one for each service on the heap. // Services used for communicating with different components of the NS1 API. - APIKeys *APIKeysService - DataFeeds *DataFeedsService - DataSources *DataSourcesService - Jobs *JobsService - MonitorRegions *MonitorRegionsService - PulsarJobs *PulsarJobsService - Notifications *NotificationsService - Records *RecordsService - Applications *ApplicationsService - RecordSearch *RecordSearchService - ZoneSearch *ZoneSearchService - Settings *SettingsService - Stats *StatsService - Teams *TeamsService - Users *UsersService - Warnings *WarningsService - Zones *ZonesService - Versions *VersionsService - DNSSEC *DNSSECService - IPAM *IPAMService - ScopeGroup *ScopeGroupService - Scope *ScopeService - Reservation *ReservationService - OptionDef *OptionDefService - TSIG *TsigService - View *DNSViewService - Network *NetworkService - GlobalIPWhitelist *GlobalIPWhitelistService - Datasets *DatasetsService - Activity *ActivityService + APIKeys *APIKeysService + DataFeeds *DataFeedsService + DataSources *DataSourcesService + Jobs *JobsService + MonitorRegions *MonitorRegionsService + PulsarJobs *PulsarJobsService + Notifications *NotificationsService + Records *RecordsService + Applications *ApplicationsService + RecordSearch *RecordSearchService + ZoneSearch *ZoneSearchService + Settings *SettingsService + Stats *StatsService + Teams *TeamsService + Users *UsersService + Warnings *WarningsService + Zones *ZonesService + Versions *VersionsService + DNSSEC *DNSSECService + IPAM *IPAMService + ScopeGroup *ScopeGroupService + Scope *ScopeService + Reservation *ReservationService + OptionDef *OptionDefService + TSIG *TsigService + View *DNSViewService + Network *NetworkService + GlobalIPWhitelist *GlobalIPWhitelistService + Datasets *DatasetsService + Activity *ActivityService + Redirects *RedirectService + RedirectCertificates *RedirectCertificateService } // NewClient constructs and returns a reference to an instantiated Client. @@ -141,6 +142,8 @@ func NewClient(httpClient Doer, options ...func(*Client)) *Client { c.GlobalIPWhitelist = (*GlobalIPWhitelistService)(&c.common) c.Datasets = (*DatasetsService)(&c.common) c.Activity = (*ActivityService)(&c.common) + c.Redirects = (*RedirectService)(&c.common) + c.RedirectCertificates = (*RedirectCertificateService)(&c.common) for _, option := range options { option(c) @@ -187,7 +190,7 @@ func SetDDIAPI() func(*Client) { return func(c *Client) { c.DDI = true } } -// Param is a container struct which holds a `Key` and `Value` field corresponding to the values of a URL parameter. +// Param is a container struct which holds a `Key` and `Value` field corresponding to the values of a URL parameter. type Param struct { Key, Value string } @@ -316,7 +319,7 @@ func CheckResponse(resp *http.Response) error { restErr := &Error{Resp: resp} - msgBody, err := ioutil.ReadAll(resp.Body) + msgBody, err := io.ReadAll(resp.Body) if err != nil { return err } diff --git a/rest/dataset_test.go b/rest/dataset_test.go index 520c8c2..ed51a29 100644 --- a/rest/dataset_test.go +++ b/rest/dataset_test.go @@ -1,13 +1,14 @@ package rest_test import ( + "net/http" + "testing" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "gopkg.in/ns1/ns1-go.v2/mockns1" api "gopkg.in/ns1/ns1-go.v2/rest" "gopkg.in/ns1/ns1-go.v2/rest/model/dataset" - "net/http" - "testing" ) func TestDatasetsService(t *testing.T) { diff --git a/rest/model/dataset/dataset_test.go b/rest/model/dataset/dataset_test.go index 6e49c40..f53b763 100644 --- a/rest/model/dataset/dataset_test.go +++ b/rest/model/dataset/dataset_test.go @@ -2,10 +2,11 @@ package dataset import ( "encoding/json" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" "testing" "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) var now = time.Unix(time.Now().Unix(), 0) diff --git a/rest/model/dns/record_test.go b/rest/model/dns/record_test.go index 428a71d..ea68474 100644 --- a/rest/model/dns/record_test.go +++ b/rest/model/dns/record_test.go @@ -41,7 +41,7 @@ func TestMarshalRecords(t *testing.T) { if err != nil { t.Error(err) } - if bytes.Compare(result, tt.out) != 0 { + if !bytes.Equal(result, tt.out) { t.Errorf("got %q, want %q", result, tt.out) } }) @@ -82,7 +82,7 @@ func TestMarshalRecordsOverrideTTL(t *testing.T) { if err != nil { t.Error(err) } - if bytes.Compare(result, tt.out) != 0 { + if !bytes.Equal(result, tt.out) { t.Errorf("got %q, want %q", result, tt.out) } }) diff --git a/rest/model/pulsar/application_test.go b/rest/model/pulsar/application_test.go index 21f2dd4..21883c2 100644 --- a/rest/model/pulsar/application_test.go +++ b/rest/model/pulsar/application_test.go @@ -1,8 +1,9 @@ package pulsar import ( - "github.com/stretchr/testify/assert" "testing" + + "github.com/stretchr/testify/assert" ) func TestNewBBPulsarApplication(t *testing.T) { diff --git a/rest/model/redirect/certificate.go b/rest/model/redirect/certificate.go new file mode 100644 index 0000000..23df9de --- /dev/null +++ b/rest/model/redirect/certificate.go @@ -0,0 +1,30 @@ +package redirect + +// Certificate represents an NS1 redirect certificate object +type Certificate struct { + ID *string `json:"id,omitempty"` + Domain string `json:"domain,omitempty"` + Certificate *string `json:"certificate,omitempty"` + ValidFrom *int64 `json:"valid_from,omitempty"` + ValidUntil *int64 `json:"valid_until,omitempty"` + Processing *bool `json:"processing,omitempty"` + Errors *string `json:"errors,omitempty"` + LastUpdated *int64 `json:"last_updated,omitempty"` +} + +// CertificateList represents an NS1 redirect certificate list object +type CertificateList struct { + After *string `json:"after,omitempty"` + Count int64 `json:"count,omitempty"` + Limit *int64 `json:"limit,omitempty"` + Results []*Certificate `json:"results"` + Total int64 `json:"total,omitempty"` +} + +// NewCertificate creates a new redirect certificate object with the given domain +func NewCertificate(domain string) *Certificate { + cert := Certificate{ + Domain: domain, + } + return &cert +} diff --git a/rest/model/redirect/certificate_test.go b/rest/model/redirect/certificate_test.go new file mode 100644 index 0000000..7d1739a --- /dev/null +++ b/rest/model/redirect/certificate_test.go @@ -0,0 +1,65 @@ +package redirect + +import ( + "encoding/json" + "strconv" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestNewCertificate(t *testing.T) { + domain := "www.mydomain.com" + cert := NewCertificate(domain) + assert.Equal(t, &Certificate{Domain: domain}, cert, "certificate mismatch") +} + +func TestUnmarshalCertificate(t *testing.T) { + id := "313829b3-0861-44b1-b92a-cdd49d555a83" + domain := "www.mydomain.com" + certificate := "something" + processing := false + errors := "failed renewing certificate" + updated := time.Now().Unix() + since := updated - 3600 + until := updated + 3600 + d := []byte(`{ + "id": "` + id + `", + "domain": "` + domain + `", + "certificate": "` + certificate + `", + "valid_from": ` + strconv.FormatInt(since, 10) + `, + "valid_until": ` + strconv.FormatInt(until, 10) + `, + "processing": ` + strconv.FormatBool(processing) + `, + "errors": "` + errors + `", + "last_updated": ` + strconv.FormatInt(updated, 10) + ` + }`) + cert := Certificate{} + err := json.Unmarshal(d, &cert) + assert.NoError(t, err, "unmarshalling error") + assert.NotNil(t, cert.ID, "nil id") + assert.Equal(t, id, *cert.ID, "id mismatch") + assert.Equal(t, domain, cert.Domain, "domain mismatch") + assert.NotNil(t, cert.Certificate, "nil certificate") + assert.Equal(t, certificate, *cert.Certificate, "certificate mismatch") + assert.NotNil(t, cert.ValidFrom, "nil valid_from") + assert.Equal(t, since, *cert.ValidFrom, "valid_from mismatch") + assert.NotNil(t, cert.ValidUntil, "nil valid_until") + assert.Equal(t, until, *cert.ValidUntil, "valid_until mismatch") + assert.NotNil(t, cert.Processing, "nil processing") + assert.Equal(t, processing, *cert.Processing, "processing mismatch") + assert.NotNil(t, cert.Errors, "nil errors") + assert.Equal(t, errors, *cert.Errors, "errors mismatch") + assert.NotNil(t, cert.LastUpdated, "nil last_updated") + assert.Equal(t, updated, *cert.LastUpdated, "last_updated mismatch") +} + +func TestMarshalCertificate(t *testing.T) { + domain := "www.mydomain.com" + cert := NewCertificate(domain) + d, err := json.Marshal(&cert) + assert.NoError(t, err, "marshalling error") + assert.JSONEq(t, `{ + "domain": "`+domain+`" + }`, string(d), "json mismatch") +} diff --git a/rest/model/redirect/configuration.go b/rest/model/redirect/configuration.go new file mode 100644 index 0000000..ae6ea7d --- /dev/null +++ b/rest/model/redirect/configuration.go @@ -0,0 +1,128 @@ +package redirect + +// Certificate represents an NS1 redirect configuration object +type Configuration struct { + ID *string `json:"id,omitempty"` + CertificateID *string `json:"certificate_id,omitempty"` + Domain string `json:"domain,omitempty"` + Path string `json:"path,omitempty"` + Target string `json:"target,omitempty"` + Tags []string `json:"tags"` + ForwardingMode *ForwardingMode `json:"forwarding_mode,omitempty"` + ForwardingType *ForwardingType `json:"forwarding_type,omitempty"` + HttpsEnabled *bool `json:"https_enabled,omitempty"` + HttpsForced *bool `json:"https_forced,omitempty"` + QueryForwarding *bool `json:"query_forwarding,omitempty"` + LastUpdated *int64 `json:"last_updated,omitempty"` +} + +// ConfigurationList represents an NS1 redirect configuration list object +type ConfigurationList struct { + After *string `json:"after,omitempty"` + Count int64 `json:"count,omitempty"` + Limit *int64 `json:"limit,omitempty"` + Results []*Configuration `json:"results"` + Total int64 `json:"total,omitempty"` +} + +// NewConfiguration creates a new configuration with the given parameters +func NewConfiguration( + domain string, + path string, + target string, + tags []string, + fwMode *ForwardingMode, + fwType *ForwardingType, + httpsEnabled *bool, + httpsForced *bool, + queryFwd *bool, +) *Configuration { + cfg := Configuration{ + Domain: domain, + Path: path, + Target: target, + Tags: tags, + ForwardingMode: fwMode, + ForwardingType: fwType, + HttpsEnabled: httpsEnabled, + HttpsForced: httpsForced, + QueryForwarding: queryFwd, + } + return &cfg +} + +// NewConfiguration creates a new configuration with the given parameters +func NewConfigurationMinimal(domain string, path string, target string) *Configuration { + cfg := Configuration{ + Domain: domain, + Path: path, + Target: target, + } + return &cfg +} + +// ForwardingMode is a string enum +type ForwardingMode string + +const ( + All ForwardingMode = "all" + Capture ForwardingMode = "capture" + None ForwardingMode = "none" +) + +func (s ForwardingMode) String() string { + switch s { + case All: + return "all" + case Capture: + return "capture" + case None: + return "none" + } + return "unknown" +} + +func ParseForwardingMode(str string) (ForwardingMode, bool) { + switch str { + case "all": + return All, true + case "capture": + return Capture, true + case "none": + return None, true + } + return "unknown", false +} + +// ForwardingType is a string enum +type ForwardingType string + +const ( + Masking ForwardingType = "masking" + Permanent ForwardingType = "permanent" + Temporary ForwardingType = "temporary" +) + +func (s ForwardingType) String() string { + switch s { + case Masking: + return "masking" + case Permanent: + return "permanent" + case Temporary: + return "temporary" + } + return "unknown" +} + +func ParseForwardingType(str string) (ForwardingType, bool) { + switch str { + case "masking": + return Masking, true + case "permanent": + return Permanent, true + case "temporary": + return Temporary, true + } + return "unknown", false +} diff --git a/rest/model/redirect/configuration_test.go b/rest/model/redirect/configuration_test.go new file mode 100644 index 0000000..d398038 --- /dev/null +++ b/rest/model/redirect/configuration_test.go @@ -0,0 +1,123 @@ +package redirect + +import ( + "encoding/json" + "strconv" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestNewConfiguration(t *testing.T) { + fwMode := All + fwType := Permanent + cfg := Configuration{ + Domain: "www.mydomain.com", + Path: "/path", + Target: "https://google.com", + ForwardingMode: &fwMode, + ForwardingType: &fwType, + } + got := NewConfiguration(cfg.Domain, cfg.Path, cfg.Target, cfg.Tags, cfg.ForwardingMode, cfg.ForwardingType, cfg.HttpsEnabled, cfg.HttpsForced, cfg.QueryForwarding) + assert.Equal(t, &cfg, got, "Configuration mismatch") +} + +func TestNewConfigurationMinimal(t *testing.T) { + cfg := Configuration{ + Domain: "www.mydomain.com", + Path: "/path", + Target: "https://google.com", + } + got := NewConfigurationMinimal(cfg.Domain, cfg.Path, cfg.Target) + assert.Equal(t, &cfg, got, "Configuration mismatch") +} + +func TestUnmarshalConfiguration(t *testing.T) { + id := "313829b3-0861-44b1-b92a-cdd49d555a83" + certId := "7b3c6d8b-721b-4a61-9520-84cfae07cb94" + domain := "www.mydomain.com" + path := "/*" + target := "https://www.google.com" + fwMode := Capture + fwType := Temporary + https := true + force := true + queryFwd := true + tag := "aaa" + updated := time.Now().Unix() + d := []byte(`{ + "id": "` + id + `", + "certificate_id": "` + certId + `", + "domain": "` + domain + `", + "path": "` + path + `", + "target": "` + target + `", + "forwarding_mode": "` + fwMode.String() + `", + "forwarding_type": "` + fwType.String() + `", + "https_enabled": ` + strconv.FormatBool(https) + `, + "https_forced": ` + strconv.FormatBool(force) + `, + "query_forwarding": ` + strconv.FormatBool(queryFwd) + `, + "tags": [ + "` + tag + `" + ], + "last_updated": ` + strconv.FormatInt(updated, 10) + ` + }`) + cfg := Configuration{} + err := json.Unmarshal(d, &cfg) + assert.NoError(t, err, "unmarshalling error") + assert.NotNil(t, cfg.ID, "nil id") + assert.Equal(t, id, *cfg.ID, "id mismatch") + assert.NotNil(t, cfg.CertificateID, "nil certificate id") + assert.Equal(t, certId, *cfg.CertificateID, "certificate_id mismatch") + assert.Equal(t, domain, cfg.Domain, "domain mismatch") + assert.Equal(t, path, cfg.Path, "path mismatch") + assert.Equal(t, target, cfg.Target, "target mismatch") + assert.NotNil(t, cfg.ForwardingMode, "nil forwarding_mode") + assert.Equal(t, fwMode, *cfg.ForwardingMode, "forwarding_mode mismatch") + assert.NotNil(t, cfg.ForwardingType, "nil forwarding_type") + assert.Equal(t, fwType, *cfg.ForwardingType, "forwarding_type mismatch") + assert.NotNil(t, cfg.HttpsEnabled, "nil https_enabled") + assert.Equal(t, https, *cfg.HttpsEnabled, "https_enabled mismatch") + assert.NotNil(t, cfg.HttpsForced, "nil https_forced") + assert.Equal(t, force, *cfg.HttpsForced, "https_forced mismatch") + assert.NotNil(t, cfg.QueryForwarding, "nil query_forwarding") + assert.Equal(t, queryFwd, *cfg.QueryForwarding, "query_forwarding mismatch") + assert.Len(t, cfg.Tags, 1, "tag size mismatch") + assert.Equal(t, tag, cfg.Tags[0], "tags mismatch") + assert.NotNil(t, cfg.LastUpdated, "nil last_updated") + assert.Equal(t, updated, *cfg.LastUpdated, "last_updated mismatch") +} + +func TestMarshalConfiguration(t *testing.T) { + fwMode := None + fwType := Masking + https := true + force := true + queryFwd := true + cfg := NewConfiguration( + "www.mydomain.com", + "/path", + "https://google.com", + []string{"a", "b"}, + &fwMode, + &fwType, + &https, + &force, + &queryFwd, + ) + d, err := json.Marshal(&cfg) + assert.NoError(t, err, "marshalling error") + assert.JSONEq(t, `{ + "domain": "`+cfg.Domain+`", + "path": "`+cfg.Path+`", + "target": "`+cfg.Target+`", + "forwarding_mode": "`+cfg.ForwardingMode.String()+`", + "forwarding_type": "`+cfg.ForwardingType.String()+`", + "https_enabled": `+strconv.FormatBool(https)+`, + "https_forced": `+strconv.FormatBool(force)+`, + "query_forwarding": `+strconv.FormatBool(queryFwd)+`, + "tags": [ + "`+cfg.Tags[0]+`", "`+cfg.Tags[1]+`" + ] + }`, string(d), "json mismatch") +} diff --git a/rest/optiondef.go b/rest/optiondef.go index a3ad0c0..ace2b96 100644 --- a/rest/optiondef.go +++ b/rest/optiondef.go @@ -3,8 +3,9 @@ package rest import ( "errors" "fmt" - "gopkg.in/ns1/ns1-go.v2/rest/model/dhcp" "net/http" + + "gopkg.in/ns1/ns1-go.v2/rest/model/dhcp" ) // OptionDefService handles the 'scope group' endpoints. diff --git a/rest/optiondef_test.go b/rest/optiondef_test.go index f24c1b0..096999b 100644 --- a/rest/optiondef_test.go +++ b/rest/optiondef_test.go @@ -1,10 +1,11 @@ package rest_test import ( - "gopkg.in/ns1/ns1-go.v2/rest/model/dhcp" "net/http" "testing" + "gopkg.in/ns1/ns1-go.v2/rest/model/dhcp" + "gopkg.in/ns1/ns1-go.v2/mockns1" api "gopkg.in/ns1/ns1-go.v2/rest" ) diff --git a/rest/redirect.go b/rest/redirect.go new file mode 100644 index 0000000..b62a8d9 --- /dev/null +++ b/rest/redirect.go @@ -0,0 +1,179 @@ +package rest + +import ( + "errors" + "fmt" + "net/http" + "strings" + + "gopkg.in/ns1/ns1-go.v2/rest/model/redirect" +) + +// RedirectService handles 'redirect' endpoint. +type RedirectService service + +// List returns the configured redirects. +// +// NS1 API docs: https://developer.ibm.com/apis/catalog/ns1--ibm-ns1-connect-api/Getting+Started +// Feature docs: https://www.ibm.com/docs/en/ns1-connect?topic=url-redirects +func (s *RedirectService) List() ([]*redirect.Configuration, *http.Response, error) { + req, err := s.client.NewRequest("GET", "redirect", nil) + if err != nil { + return nil, nil, err + } + + cfgList := redirect.ConfigurationList{} + var resp *http.Response + if s.client.FollowPagination { + resp, err = s.client.DoWithPagination(req, &cfgList, s.nextCfgs) + } else { + resp, err = s.client.Do(req, &cfgList) + } + if err != nil { + return nil, resp, err + } + + return cfgList.Results, resp, nil +} + +// Get takes a redirect config id and returns a single config. +// +// NS1 API docs: https://developer.ibm.com/apis/catalog/ns1--ibm-ns1-connect-api/Getting+Started +// Feature docs: https://www.ibm.com/docs/en/ns1-connect?topic=url-redirects +func (s *RedirectService) Get(cfgId string) (*redirect.Configuration, *http.Response, error) { + path := fmt.Sprintf("redirect/%s", cfgId) + + req, err := s.client.NewRequest("GET", path, nil) + if err != nil { + return nil, nil, err + } + + var cfg redirect.Configuration + var resp *http.Response + resp, err = s.client.Do(req, &cfg) + if err != nil { + switch err := err.(type) { + case *Error: + if strings.HasSuffix(err.Message, " not found") { + return nil, resp, ErrRedirectNotFound + } + } + return nil, resp, err + } + + return &cfg, resp, nil +} + +// Create takes a *Configuration and creates a new redirect. +// +// NS1 API docs: https://developer.ibm.com/apis/catalog/ns1--ibm-ns1-connect-api/Getting+Started +// Feature docs: https://www.ibm.com/docs/en/ns1-connect?topic=url-redirects +func (s *RedirectService) Create(cfg *redirect.Configuration) (*redirect.Configuration, *http.Response, error) { + if cfg == nil { + return nil, nil, ErrRedirectNil + } + + req, err := s.client.NewRequest("PUT", "redirect", &cfg) + if err != nil { + return nil, nil, err + } + + // Update redirect fields with data from api(ensure consistent) + resp, err := s.client.Do(req, &cfg) + if err != nil { + switch err := err.(type) { + case *Error: + if err.Message == "configuration already exists" { + return nil, resp, ErrRedirectExists + } + } + return nil, resp, err + } + + return cfg, resp, nil +} + +// Update takes a *Configuration and modifies basic details of a redirect. +// +// NS1 API docs: https://developer.ibm.com/apis/catalog/ns1--ibm-ns1-connect-api/Getting+Started +// Feature docs: https://www.ibm.com/docs/en/ns1-connect?topic=url-redirects +func (s *RedirectService) Update(cfg *redirect.Configuration) (*redirect.Configuration, *http.Response, error) { + if cfg == nil || cfg.ID == nil { + return nil, nil, ErrRedirectNil + } + + path := fmt.Sprintf("redirect/%s", *cfg.ID) + + req, err := s.client.NewRequest("POST", path, &cfg) + if err != nil { + return nil, nil, err + } + + // Update redirect fields with data from api(ensure consistent) + resp, err := s.client.Do(req, &cfg) + if err != nil { + switch err := err.(type) { + case *Error: + if strings.HasSuffix(err.Message, " not found") { + return nil, resp, ErrRedirectNotFound + } + } + return nil, resp, err + } + + return cfg, resp, nil +} + +// Delete takes a configuration id and destroys the associated redirect configuration. +// +// NS1 API docs: https://developer.ibm.com/apis/catalog/ns1--ibm-ns1-connect-api/Getting+Started +// Feature docs: https://www.ibm.com/docs/en/ns1-connect?topic=url-redirects +func (s *RedirectService) Delete(cfgId string) (*http.Response, error) { + path := fmt.Sprintf("redirect/%s", cfgId) + + req, err := s.client.NewRequest("DELETE", path, nil) + if err != nil { + return nil, err + } + + resp, err := s.client.Do(req, nil) + if err != nil { + switch err := err.(type) { + case *Error: + if strings.HasSuffix(err.Message, " not found") { + return resp, ErrRedirectNotFound + } + } + return resp, err + } + + return resp, nil +} + +// nextCfgs is a pagination helper than gets and appends another list of redirect configs +// to the passed list. +func (s *RedirectService) nextCfgs(v *interface{}, uri string) (*http.Response, error) { + tmpCfgList := redirect.ConfigurationList{} + resp, err := s.client.getURI(&tmpCfgList, uri) + if err != nil { + return resp, err + } + cfgList, ok := (*v).(*redirect.ConfigurationList) + if !ok { + return nil, fmt.Errorf( + "incorrect value for v, expected value of type *redirect.ConfigurationList, got: %T", v, + ) + } + cfgList.Total = tmpCfgList.Total + cfgList.Count += tmpCfgList.Count + cfgList.Results = append(cfgList.Results, tmpCfgList.Results...) + return resp, nil +} + +var ( + ErrRedirectNil = errors.New("parameter missing") + // ErrRedirectExists bundles PUT create error. + ErrRedirectExists = errors.New("redirect configuration id already exists") + // ErrRedirectNotFound bundles GET/POST/DELETE error. + ErrRedirectNotFound = errors.New("redirect configuration id not found") +) diff --git a/rest/redirect_certificate.go b/rest/redirect_certificate.go new file mode 100644 index 0000000..a4f831c --- /dev/null +++ b/rest/redirect_certificate.go @@ -0,0 +1,175 @@ +package rest + +import ( + "errors" + "fmt" + "net/http" + "strings" + + "gopkg.in/ns1/ns1-go.v2/rest/model/redirect" +) + +// RedirectCertificateService handles 'redirect/certificates' endpoint. +type RedirectCertificateService service + +// List returns the existing redirect certificates. +// +// NS1 API docs: https://developer.ibm.com/apis/catalog/ns1--ibm-ns1-connect-api/Getting+Started +// Feature docs: https://www.ibm.com/docs/en/ns1-connect?topic=url-redirects +func (s *RedirectCertificateService) List() ([]*redirect.Certificate, *http.Response, error) { + req, err := s.client.NewRequest("GET", "redirect/certificates", nil) + if err != nil { + return nil, nil, err + } + + certList := redirect.CertificateList{} + var resp *http.Response + if s.client.FollowPagination { + resp, err = s.client.DoWithPagination(req, &certList, s.nextCerts) + } else { + resp, err = s.client.Do(req, &certList) + } + if err != nil { + return nil, resp, err + } + + return certList.Results, resp, nil +} + +// Get takes a redirect config id and returns a single config. +// +// NS1 API docs: https://developer.ibm.com/apis/catalog/ns1--ibm-ns1-connect-api/Getting+Started +// Feature docs: https://www.ibm.com/docs/en/ns1-connect?topic=url-redirects +func (s *RedirectCertificateService) Get(certId string) (*redirect.Certificate, *http.Response, error) { + path := fmt.Sprintf("redirect/certificates/%s", certId) + + req, err := s.client.NewRequest("GET", path, nil) + if err != nil { + return nil, nil, err + } + + var cert redirect.Certificate + var resp *http.Response + resp, err = s.client.Do(req, &cert) + if err != nil { + switch err := err.(type) { + case *Error: + if strings.HasSuffix(err.Message, " not found") { + return nil, resp, ErrRedirectCertificateNotFound + } + } + return nil, resp, err + } + + return &cert, resp, nil +} + +// Create takes a *Certificate and creates a new redirect. +// +// NS1 API docs: https://developer.ibm.com/apis/catalog/ns1--ibm-ns1-connect-api/Getting+Started +// Feature docs: https://www.ibm.com/docs/en/ns1-connect?topic=url-redirects +func (s *RedirectCertificateService) Create(domain string) (*redirect.Certificate, *http.Response, error) { + + req, err := s.client.NewRequest("PUT", "redirect/certificates", redirect.NewCertificate(domain)) + if err != nil { + return nil, nil, err + } + + // Update redirect fields with data from api(ensure consistent) + var cert redirect.Certificate + resp, err := s.client.Do(req, &cert) + if err != nil { + switch err := err.(type) { + case *Error: + if err.Message == "certificate already exists" { + return nil, resp, ErrRedirectCertificateExists + } + } + return nil, resp, err + } + + return &cert, resp, nil +} + +// Update takes a certificate id and requests it to be renewed. +// +// NS1 API docs: https://developer.ibm.com/apis/catalog/ns1--ibm-ns1-connect-api/Getting+Started +// Feature docs: https://www.ibm.com/docs/en/ns1-connect?topic=url-redirects +func (s *RedirectCertificateService) Update(certId string) (*http.Response, error) { + + path := fmt.Sprintf("redirect/certificates/%s", certId) + + req, err := s.client.NewRequest("POST", path, nil) + if err != nil { + return nil, err + } + + // Update redirect fields with data from api(ensure consistent) + resp, err := s.client.Do(req, nil) + if err != nil { + switch err := err.(type) { + case *Error: + if strings.HasSuffix(err.Message, " not found") { + return resp, ErrRedirectCertificateNotFound + } + } + return resp, err + } + + return resp, nil +} + +// Delete takes a certificate id and requests it to be revoked. +// +// NS1 API docs: https://developer.ibm.com/apis/catalog/ns1--ibm-ns1-connect-api/Getting+Started +// Feature docs: https://www.ibm.com/docs/en/ns1-connect?topic=url-redirects +func (s *RedirectCertificateService) Delete(certId string) (*http.Response, error) { + path := fmt.Sprintf("redirect/certificates/%s", certId) + + req, err := s.client.NewRequest("DELETE", path, nil) + if err != nil { + return nil, err + } + + resp, err := s.client.Do(req, nil) + if err != nil { + switch err := err.(type) { + case *Error: + if strings.HasSuffix(err.Message, " not found") { + return resp, ErrRedirectCertificateNotFound + } + } + return resp, err + } + + return resp, nil +} + +// nextCerts is a pagination helper than gets and appends another list of redirect configs +// to the passed list. +func (s *RedirectCertificateService) nextCerts(v *interface{}, uri string) (*http.Response, error) { + tmpcertList := redirect.CertificateList{} + resp, err := s.client.getURI(&tmpcertList, uri) + if err != nil { + return resp, err + } + + certList, ok := (*v).(*redirect.CertificateList) + if !ok { + return nil, fmt.Errorf( + "incorrect value for v, expected value of type *redirect.CertificateList, got: %T", v, + ) + } + certList.Total = tmpcertList.Total + certList.Count += tmpcertList.Count + certList.Results = append(certList.Results, tmpcertList.Results...) + return resp, nil +} + +var ( + ErrRedirectCertificateNil = errors.New("parameter missing") + // ErrRedirectCertificateExists bundles PUT create error. + ErrRedirectCertificateExists = errors.New("redirect certificate id already exists") + // ErrRedirectCertificateNotFound bundles GET/POST/DELETE error. + ErrRedirectCertificateNotFound = errors.New("redirect certificate id not found") +) diff --git a/rest/redirect_certificate_test.go b/rest/redirect_certificate_test.go new file mode 100644 index 0000000..e29544f --- /dev/null +++ b/rest/redirect_certificate_test.go @@ -0,0 +1,226 @@ +package rest_test + +import ( + "net/http" + "testing" + + "github.com/stretchr/testify/require" + "gopkg.in/ns1/ns1-go.v2/mockns1" + api "gopkg.in/ns1/ns1-go.v2/rest" + "gopkg.in/ns1/ns1-go.v2/rest/model/redirect" +) + +func TestRedirectCertificateService(t *testing.T) { + mock, doer, err := mockns1.New(t) + require.Nil(t, err) + defer mock.Shutdown() + + client := api.NewClient(doer, api.SetEndpoint("https://"+mock.Address+"/v1/")) + + id := "87461586-c681-43b7-b283-f840be95e13c" + + t.Run("List", func(t *testing.T) { + t.Run("Pagination", func(t *testing.T) { + defer mock.ClearTestCases() + + client.FollowPagination = true + certList := &redirect.CertificateList{ + Count: 4, + Total: 4, + Results: []*redirect.Certificate{ + {Domain: "a.com"}, + {Domain: "b.com"}, + {Domain: "c.com"}, + {Domain: "d.com"}, + }, + } + require.Nil(t, mock.AddRedirectCertificateListTestCase(nil, nil, certList)) + + respcerts, _, err := client.RedirectCertificates.List() + require.Nil(t, err) + require.NotNil(t, respcerts) + require.Equal(t, len(certList.Results), len(respcerts)) + + for i := range certList.Results { + require.Equal(t, certList.Results[i].Domain, respcerts[i].Domain, i) + } + }) + + t.Run("No Pagination", func(t *testing.T) { + defer mock.ClearTestCases() + + client.FollowPagination = false + certList := &redirect.CertificateList{ + Count: 2, + Total: 4, + Results: []*redirect.Certificate{ + {Domain: "a.com"}, + {Domain: "b.com"}, + }, + } + + header := http.Header{} + header.Set("Link", `; rel="next"`) + + require.Nil(t, mock.AddRedirectCertificateListTestCase(nil, header, certList)) + + respcerts, resp, err := client.RedirectCertificates.List() + require.Nil(t, err) + require.NotNil(t, respcerts) + require.Equal(t, len(certList.Results), len(respcerts)) + require.Contains(t, resp.Header.Get("Link"), "redirect/certificates?after=b.com") + + for i := range certList.Results { + require.Equal(t, certList.Results[i].Domain, respcerts[i].Domain, i) + } + }) + + t.Run("Error", func(t *testing.T) { + t.Run("HTTP", func(t *testing.T) { + defer mock.ClearTestCases() + + require.Nil(t, mock.AddTestCase( + http.MethodGet, "/redirect/certificates", http.StatusNotFound, + nil, nil, "", `{"message": "test error"}`, + )) + + respcerts, resp, err := client.RedirectCertificates.List() + require.Nil(t, respcerts) + require.NotNil(t, err) + require.Contains(t, err.Error(), "test error") + require.Equal(t, http.StatusNotFound, resp.StatusCode) + }) + + t.Run("Other", func(t *testing.T) { + c := api.NewClient(errorClient{}, api.SetEndpoint("")) + respcerts, resp, err := c.RedirectCertificates.List() + require.Nil(t, resp) + require.Error(t, err) + require.Nil(t, respcerts) + }) + }) + }) + + t.Run("Get", func(t *testing.T) { + + t.Run("Pagination", func(t *testing.T) { + defer mock.ClearTestCases() + + client.FollowPagination = true + cert := &redirect.Certificate{ + Domain: "a.com", + } + require.Nil(t, mock.AddRedirectCertificateGetTestCase(id, nil, nil, cert)) + + respcert, _, err := client.RedirectCertificates.Get(id) + require.Nil(t, err) + require.NotNil(t, respcert) + require.Equal(t, cert.Domain, respcert.Domain) + }) + + t.Run("Error", func(t *testing.T) { + t.Run("HTTP", func(t *testing.T) { + defer mock.ClearTestCases() + + require.Nil(t, mock.AddTestCase( + http.MethodGet, "/redirect/certificates/"+id, http.StatusNotFound, + nil, nil, "", `{"message": "test error"}`, + )) + + cert, resp, err := client.RedirectCertificates.Get(id) + require.Nil(t, cert) + require.NotNil(t, err) + require.Contains(t, err.Error(), "test error") + require.Equal(t, http.StatusNotFound, resp.StatusCode) + }) + + t.Run("Other", func(t *testing.T) { + c := api.NewClient(errorClient{}, api.SetEndpoint("")) + cert, resp, err := c.RedirectCertificates.Get(id) + require.Nil(t, resp) + require.Error(t, err) + require.Nil(t, cert) + }) + }) + }) + + t.Run("Create", func(t *testing.T) { + req := &redirect.Certificate{Domain: "a.com"} + cert := &redirect.Certificate{ + ID: &id, + Domain: "a.com", + } + + t.Run("Success", func(t *testing.T) { + defer mock.ClearTestCases() + + require.Nil(t, mock.AddRedirectCertificateCreateTestCase(nil, nil, req, cert)) + + _, _, err := client.RedirectCertificates.Create(cert.Domain) + require.Nil(t, err) + }) + + t.Run("Error", func(t *testing.T) { + defer mock.ClearTestCases() + + require.Nil(t, mock.AddTestCase( + http.MethodPut, "/redirect/certificates", http.StatusConflict, + nil, nil, req, `{"message": "certificate already exists"}`, + )) + + _, _, err := client.RedirectCertificates.Create(cert.Domain) + require.Equal(t, api.ErrRedirectCertificateExists, err) + }) + }) + + t.Run("Update", func(t *testing.T) { + cert := &redirect.Certificate{ + ID: &id, + Domain: "a.com", + } + + t.Run("Success", func(t *testing.T) { + defer mock.ClearTestCases() + + require.Nil(t, mock.AddRedirectCertificateUpdateTestCase(nil, nil, id, cert)) + + _, err := client.RedirectCertificates.Update(id) + require.Nil(t, err) + }) + + t.Run("Error", func(t *testing.T) { + defer mock.ClearTestCases() + + require.Nil(t, mock.AddTestCase( + http.MethodPost, "/redirect/certificates/"+id, http.StatusNotFound, + nil, nil, "", `{"message": "certificate not found"}`, + )) + + _, err := client.RedirectCertificates.Update(id) + require.Equal(t, api.ErrRedirectCertificateNotFound, err) + }) + }) + + t.Run("Delete", func(t *testing.T) { + t.Run("Success", func(t *testing.T) { + defer mock.ClearTestCases() + + require.Nil(t, mock.AddRedirectCertificateDeleteTestCase(id, nil, nil)) + + _, err := client.RedirectCertificates.Delete(id) + require.Nil(t, err) + }) + + t.Run("Error", func(t *testing.T) { + defer mock.ClearTestCases() + + require.Nil(t, mock.AddTestCase( + http.MethodDelete, "/redirect/certificates/"+id, http.StatusNotFound, + nil, nil, "", `{"message": "certificate not found"}`, + )) + + _, err := client.RedirectCertificates.Delete(id) + require.Equal(t, api.ErrRedirectCertificateNotFound, err) + }) + }) +} diff --git a/rest/redirect_test.go b/rest/redirect_test.go new file mode 100644 index 0000000..a2dec8e --- /dev/null +++ b/rest/redirect_test.go @@ -0,0 +1,223 @@ +package rest_test + +import ( + "net/http" + "testing" + + "github.com/stretchr/testify/require" + "gopkg.in/ns1/ns1-go.v2/mockns1" + api "gopkg.in/ns1/ns1-go.v2/rest" + "gopkg.in/ns1/ns1-go.v2/rest/model/redirect" +) + +func TestRedirectService(t *testing.T) { + mock, doer, err := mockns1.New(t) + require.Nil(t, err) + defer mock.Shutdown() + + client := api.NewClient(doer, api.SetEndpoint("https://"+mock.Address+"/v1/")) + + id := "87461586-c681-43b7-b283-f840be95e13c" + + t.Run("List", func(t *testing.T) { + t.Run("Pagination", func(t *testing.T) { + defer mock.ClearTestCases() + + client.FollowPagination = true + cfgList := &redirect.ConfigurationList{ + Count: 4, + Total: 4, + Results: []*redirect.Configuration{ + {Domain: "a.com"}, + {Domain: "b.com"}, + {Domain: "c.com"}, + {Domain: "d.com"}, + }} + require.Nil(t, mock.AddRedirectListTestCase(nil, nil, cfgList)) + + respCfgs, _, err := client.Redirects.List() + require.Nil(t, err) + require.NotNil(t, respCfgs) + require.Equal(t, len(cfgList.Results), len(respCfgs)) + + for i := range cfgList.Results { + require.Equal(t, cfgList.Results[i].Domain, respCfgs[i].Domain, i) + } + }) + + t.Run("No Pagination", func(t *testing.T) { + defer mock.ClearTestCases() + + client.FollowPagination = false + cfgList := &redirect.ConfigurationList{ + Count: 2, + Total: 4, + Results: []*redirect.Configuration{ + {Domain: "a.com"}, + {Domain: "b.com"}, + }} + + header := http.Header{} + header.Set("Link", `; rel="next"`) + + require.Nil(t, mock.AddRedirectListTestCase(nil, header, cfgList)) + + respCfgs, resp, err := client.Redirects.List() + require.Nil(t, err) + require.NotNil(t, respCfgs) + require.Equal(t, len(cfgList.Results), len(respCfgs)) + require.Contains(t, resp.Header.Get("Link"), "redirect?after=b.com") + + for i := range cfgList.Results { + require.Equal(t, cfgList.Results[i].Domain, respCfgs[i].Domain, i) + } + }) + + t.Run("Error", func(t *testing.T) { + t.Run("HTTP", func(t *testing.T) { + defer mock.ClearTestCases() + + require.Nil(t, mock.AddTestCase( + http.MethodGet, "/redirect", http.StatusNotFound, + nil, nil, "", `{"message": "test error"}`, + )) + + respCfgs, resp, err := client.Redirects.List() + require.Nil(t, respCfgs) + require.NotNil(t, err) + require.Contains(t, err.Error(), "test error") + require.Equal(t, http.StatusNotFound, resp.StatusCode) + }) + + t.Run("Other", func(t *testing.T) { + c := api.NewClient(errorClient{}, api.SetEndpoint("")) + respCfgs, resp, err := c.Redirects.List() + require.Nil(t, resp) + require.Error(t, err) + require.Nil(t, respCfgs) + }) + }) + }) + + t.Run("Get", func(t *testing.T) { + + t.Run("Pagination", func(t *testing.T) { + defer mock.ClearTestCases() + + client.FollowPagination = true + cfg := &redirect.Configuration{ + Domain: "a.com", + } + require.Nil(t, mock.AddRedirectGetTestCase(id, nil, nil, cfg)) + + respCfg, _, err := client.Redirects.Get(id) + require.Nil(t, err) + require.NotNil(t, respCfg) + require.Equal(t, cfg.Domain, respCfg.Domain) + }) + + t.Run("Error", func(t *testing.T) { + t.Run("HTTP", func(t *testing.T) { + defer mock.ClearTestCases() + + require.Nil(t, mock.AddTestCase( + http.MethodGet, "/redirect/"+id, http.StatusNotFound, + nil, nil, "", `{"message": "test error"}`, + )) + + cfg, resp, err := client.Redirects.Get(id) + require.Nil(t, cfg) + require.NotNil(t, err) + require.Contains(t, err.Error(), "test error") + require.Equal(t, http.StatusNotFound, resp.StatusCode) + }) + + t.Run("Other", func(t *testing.T) { + c := api.NewClient(errorClient{}, api.SetEndpoint("")) + cfg, resp, err := c.Redirects.Get(id) + require.Nil(t, resp) + require.Error(t, err) + require.Nil(t, cfg) + }) + }) + }) + + t.Run("Create", func(t *testing.T) { + cfg := &redirect.Configuration{ + ID: &id, + Domain: "a.com", + } + + t.Run("Success", func(t *testing.T) { + defer mock.ClearTestCases() + + require.Nil(t, mock.AddRedirectCreateTestCase(nil, nil, cfg, cfg)) + + _, _, err := client.Redirects.Create(cfg) + require.Nil(t, err) + }) + + t.Run("Error", func(t *testing.T) { + defer mock.ClearTestCases() + + require.Nil(t, mock.AddTestCase( + http.MethodPut, "/redirect", http.StatusConflict, + nil, nil, cfg, `{"message": "configuration already exists"}`, + )) + + _, _, err := client.Redirects.Create(cfg) + require.Equal(t, api.ErrRedirectExists, err) + }) + }) + + t.Run("Update", func(t *testing.T) { + cfg := &redirect.Configuration{ + ID: &id, + Domain: "a.com", + } + + t.Run("Success", func(t *testing.T) { + defer mock.ClearTestCases() + + require.Nil(t, mock.AddRedirectUpdateTestCase(nil, nil, cfg, cfg)) + + _, _, err := client.Redirects.Update(cfg) + require.Nil(t, err) + }) + + t.Run("Error", func(t *testing.T) { + defer mock.ClearTestCases() + + require.Nil(t, mock.AddTestCase( + http.MethodPost, "/redirect/"+id, http.StatusNotFound, + nil, nil, cfg, `{"message": "configuration not found"}`, + )) + + _, _, err := client.Redirects.Update(cfg) + require.Equal(t, api.ErrRedirectNotFound, err) + }) + }) + + t.Run("Delete", func(t *testing.T) { + t.Run("Success", func(t *testing.T) { + defer mock.ClearTestCases() + + require.Nil(t, mock.AddRedirectDeleteTestCase(id, nil, nil)) + + _, err := client.Redirects.Delete(id) + require.Nil(t, err) + }) + + t.Run("Error", func(t *testing.T) { + defer mock.ClearTestCases() + + require.Nil(t, mock.AddTestCase( + http.MethodDelete, "/redirect/"+id, http.StatusNotFound, + nil, nil, "", `{"message": "configuration not found"}`, + )) + + _, err := client.Redirects.Delete(id) + require.Equal(t, api.ErrRedirectNotFound, err) + }) + }) +} diff --git a/rest/reservation_test.go b/rest/reservation_test.go index 18c12a6..480e673 100644 --- a/rest/reservation_test.go +++ b/rest/reservation_test.go @@ -1,10 +1,11 @@ package rest_test import ( - "gopkg.in/ns1/ns1-go.v2/rest/model/dhcp" "net/http" "testing" + "gopkg.in/ns1/ns1-go.v2/rest/model/dhcp" + "gopkg.in/ns1/ns1-go.v2/mockns1" api "gopkg.in/ns1/ns1-go.v2/rest" ) diff --git a/rest/scope_test.go b/rest/scope_test.go index 778f1df..24c24fa 100644 --- a/rest/scope_test.go +++ b/rest/scope_test.go @@ -1,10 +1,11 @@ package rest_test import ( - "gopkg.in/ns1/ns1-go.v2/rest/model/dhcp" "net/http" "testing" + "gopkg.in/ns1/ns1-go.v2/rest/model/dhcp" + "gopkg.in/ns1/ns1-go.v2/mockns1" api "gopkg.in/ns1/ns1-go.v2/rest" ) diff --git a/rest/scopegroup.go b/rest/scopegroup.go index 0315006..8912e89 100644 --- a/rest/scopegroup.go +++ b/rest/scopegroup.go @@ -3,8 +3,9 @@ package rest import ( "errors" "fmt" - "gopkg.in/ns1/ns1-go.v2/rest/model/dhcp" "net/http" + + "gopkg.in/ns1/ns1-go.v2/rest/model/dhcp" ) // ScopeGroupService handles the 'scope group' endpoints. diff --git a/rest/scopegroup_test.go b/rest/scopegroup_test.go index 34c85f2..c3a3971 100644 --- a/rest/scopegroup_test.go +++ b/rest/scopegroup_test.go @@ -1,10 +1,11 @@ package rest_test import ( - "gopkg.in/ns1/ns1-go.v2/rest/model/dhcp" "net/http" "testing" + "gopkg.in/ns1/ns1-go.v2/rest/model/dhcp" + "gopkg.in/ns1/ns1-go.v2/mockns1" api "gopkg.in/ns1/ns1-go.v2/rest" ) diff --git a/rest/version.go b/rest/version.go index 395bc2d..75647fd 100644 --- a/rest/version.go +++ b/rest/version.go @@ -2,8 +2,9 @@ package rest import ( "fmt" - "gopkg.in/ns1/ns1-go.v2/rest/model/dns" "net/http" + + "gopkg.in/ns1/ns1-go.v2/rest/model/dns" ) // VersionsService handles 'zones/ZONE/versions' related endpoints. diff --git a/rest/version_test.go b/rest/version_test.go index 07fcfef..2c0dc7b 100644 --- a/rest/version_test.go +++ b/rest/version_test.go @@ -1,12 +1,13 @@ package rest_test import ( + "net/http" + "testing" + "github.com/stretchr/testify/require" "gopkg.in/ns1/ns1-go.v2/mockns1" api "gopkg.in/ns1/ns1-go.v2/rest" "gopkg.in/ns1/ns1-go.v2/rest/model/dns" - "net/http" - "testing" ) func TestVersion(t *testing.T) {