From 29ac9cf4241b8f3da4490693e7fd3b7cedee6679 Mon Sep 17 00:00:00 2001 From: Pedro Angelo Date: Wed, 18 Aug 2021 09:44:28 -0300 Subject: [PATCH 1/6] added support for ns1 application endpoints --- rest/application.go | 115 +++++++++++++++++++++++++++++++ rest/client.go | 2 + rest/model/pulsar/application.go | 25 +++++++ 3 files changed, 142 insertions(+) create mode 100644 rest/application.go create mode 100644 rest/model/pulsar/application.go diff --git a/rest/application.go b/rest/application.go new file mode 100644 index 0000000..66901b0 --- /dev/null +++ b/rest/application.go @@ -0,0 +1,115 @@ +package rest + +import ( + "errors" + "fmt" + "net/http" + + "gopkg.in/ns1/ns1-go.v2/rest/model/pulsar" +) + +// ApplicationsService handles 'pulsar/apps/' endpoint. +type ApplicationsService service + +// Get takes a application id and returns application struct. +// +// NS1 API docs: https://ns1.com/api#get-list-pulsar-applications +func (s *ApplicationsService) Get(id string) (*pulsar.Application, *http.Response, error) { + path := fmt.Sprintf("pulsar/apps/%s", id) + + req, err := s.client.NewRequest("GET", path, nil) + if err != nil { + return nil, nil, err + } + + var a pulsar.Application + resp, err := s.client.Do(req, &a) + + if err != nil { + switch err.(type) { + case *Error: + if err.(*Error).Message == "pulsar app not found" { + return nil, resp, ErrApplicationMissing + } + } + return nil, resp, err + } + + return &a, resp, nil +} + +// Create takes a *pulsar.Application and creates a new Application. +// +// The given application must have at least the name +// NS1 API docs: https://ns1.com/api#put-create-a-pulsar-application +func (s *ApplicationsService) Create(a *pulsar.Application) (*http.Response, error) { + req, err := s.client.NewRequest("PUT", "pulsar/apps", &a) + if err != nil { + return nil, err + } + + resp, err := s.client.Do(req, &a) + + if err != nil { + panic(err) + return resp, err + } + + return resp, nil +} + +// Update takes a *pulsar.Application and updates the application with same id on Ns1. +// +// NS1 API docs: https://ns1.com/api#post-modify-an-application +func (s *ApplicationsService) Update(a *pulsar.Application) (*http.Response, error) { + path := fmt.Sprintf("pulsar/apps/%s", a.ID) + + req, err := s.client.NewRequest("POST", path, &a) + if err != nil { + return nil, err + } + + resp, err := s.client.Do(req, &a) + + if err != nil { + switch err.(type) { + case *Error: + if err.(*Error).Message == "pulsar app not found" { + return resp, ErrApplicationMissing + } + } + return resp, err + } + + return resp, nil +} + +// Delete takes a application Id, and removes an existing application +// +// NS1 API docs: https://ns1.com/api#delete-delete-a-pulsar-application +func (s *ApplicationsService) Delete(id string) (*http.Response, error) { + path := fmt.Sprintf("pulsar/apps/%s", id) + + 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.(type) { + case *Error: + if err.(*Error).Message == "pulsar app not found" { + return resp, ErrApplicationMissing + } + } + return resp, err + } + + return resp, nil +} + +var ( + // ErrApplicationMissing bundles GET/POST/DELETE error. + ErrApplicationMissing = errors.New("application does not exist") +) diff --git a/rest/client.go b/rest/client.go index 0a569a5..3d4bcac 100644 --- a/rest/client.go +++ b/rest/client.go @@ -64,6 +64,7 @@ type Client struct { Jobs *JobsService Notifications *NotificationsService Records *RecordsService + Applications *ApplicationsService Settings *SettingsService Stats *StatsService Teams *TeamsService @@ -101,6 +102,7 @@ func NewClient(httpClient Doer, options ...func(*Client)) *Client { c.Jobs = (*JobsService)(&c.common) c.Notifications = (*NotificationsService)(&c.common) c.Records = (*RecordsService)(&c.common) + c.Applications = (*ApplicationsService)(&c.common) c.Settings = (*SettingsService)(&c.common) c.Stats = (*StatsService)(&c.common) c.Teams = (*TeamsService)(&c.common) diff --git a/rest/model/pulsar/application.go b/rest/model/pulsar/application.go new file mode 100644 index 0000000..a4b0ea2 --- /dev/null +++ b/rest/model/pulsar/application.go @@ -0,0 +1,25 @@ +package pulsar + +// Application wraps an NS1 /pulsar/apps/{appid} resource +type Application struct { + ID string `json:"appid,omitempty"` + Name string `json:"name"` + Active bool `json:"active"` + BrowserWaitMillis int `json:"browser_wait_millis"` + JobsPerTransaction int `json:"jobs_per_transaction"` + DefaultConfig DefaultConfig `json:"default_config"` +} + +type DefaultConfig struct { + Http bool `json:"http"` + Https bool `json:"https"` + RequestTimeoutMillis int `json:"request_timeout_millis"` + JobTimeoutMillis int `json:"job_timeout_millis"` + UseXhr bool `json:"use_xhr"` + StaticValues bool `json:"static_values"` +} + +// NewApplication takes a application name and creates a *Application +func NewApplication (name string) *Application { + return &Application{Name: name} +} From c81c8a265434c6b718473af55793c8005d04cf4b Mon Sep 17 00:00:00 2001 From: Pedro Angelo Date: Thu, 19 Aug 2021 17:40:32 -0300 Subject: [PATCH 2/6] added unit tests --- mockns1/applicaiton.go | 66 +++++++++ rest/application.go | 28 ++-- rest/application_test.go | 199 ++++++++++++++++++++++++++ rest/model/pulsar/application_test.go | 11 ++ 4 files changed, 296 insertions(+), 8 deletions(-) create mode 100644 mockns1/applicaiton.go create mode 100644 rest/application_test.go create mode 100644 rest/model/pulsar/application_test.go diff --git a/mockns1/applicaiton.go b/mockns1/applicaiton.go new file mode 100644 index 0000000..a33d77c --- /dev/null +++ b/mockns1/applicaiton.go @@ -0,0 +1,66 @@ +package mockns1 + +import ( + "gopkg.in/ns1/ns1-go.v2/rest/model/pulsar" + "net/http" +) + +// AddApplicationTestCase sets up a test case for the api.Client.application.List() +// function +func (s *Service) AddApplicationTestCase( + requestHeaders, responseHeaders http.Header, + response []*pulsar.Application, +) error { + return s.AddTestCase( + http.MethodGet, "/pulsar/apps", http.StatusOK, requestHeaders, + responseHeaders, "", response, + ) +} + +// AddApplicationGetTestCase sets up a test case for the api.Client.application.Get() +// function +func (s *Service) AddApplicationGetTestCase( + id string, + requestHeaders, responseHeaders http.Header, + response *pulsar.Application, +) error { + return s.AddTestCase( + http.MethodGet, "/pulsar/apps/"+id, http.StatusOK, requestHeaders, + responseHeaders, "", response, + ) +} + +// AddApplicationCreateTestCase sets up a test case for the api.Client.application.Create() +// function +func (s *Service) AddApplicationCreateTestCase( + requestHeaders, responseHeaders http.Header, + application, response *pulsar.Application, +) error { + return s.AddTestCase( + http.MethodPut, "/pulsar/apps", http.StatusCreated, requestHeaders, + responseHeaders, application, response, + ) +} + +// AddApplicationUpdateTestCase sets up a test case for the api.Client.application.Update() +// function +func (s *Service) AddApplicationUpdateTestCase( + requestHeaders, responseHeaders http.Header, + application, response *pulsar.Application, +) error { + return s.AddTestCase( + http.MethodPost, "/pulsar/apps/"+application.ID, http.StatusOK, requestHeaders, + responseHeaders, application, response, + ) +} + +// AddApplicationDeleteTestCase sets up a test case for the api.Client.application.Delete() +// function +func (s *Service) AddApplicationDeleteTestCase( + id string, requestHeaders, responseHeaders http.Header, +) error { + return s.AddTestCase( + http.MethodDelete, "/pulsar/apps/"+id, http.StatusNoContent, requestHeaders, + responseHeaders, "", "", + ) +} diff --git a/rest/application.go b/rest/application.go index 66901b0..3926717 100644 --- a/rest/application.go +++ b/rest/application.go @@ -11,6 +11,25 @@ import ( // ApplicationsService handles 'pulsar/apps/' endpoint. type ApplicationsService service +// List returns all pulsar Applications +// +// NS1 API docs: https://ns1.com/api#get-list-pulsar-applications +func (s *ApplicationsService) List() ([]*pulsar.Application, *http.Response, error) { + req, err := s.client.NewRequest("GET", "pulsar/apps", nil) + if err != nil { + return nil, nil, err + } + + al := []*pulsar.Application{} + var resp *http.Response + resp, err = s.client.Do(req, &al) + if err != nil { + return nil, resp, err + } + + return al, resp, nil +} + // Get takes a application id and returns application struct. // // NS1 API docs: https://ns1.com/api#get-list-pulsar-applications @@ -47,15 +66,8 @@ func (s *ApplicationsService) Create(a *pulsar.Application) (*http.Response, err if err != nil { return nil, err } - resp, err := s.client.Do(req, &a) - - if err != nil { - panic(err) - return resp, err - } - - return resp, nil + return resp, err } // Update takes a *pulsar.Application and updates the application with same id on Ns1. diff --git a/rest/application_test.go b/rest/application_test.go new file mode 100644 index 0000000..40bcd79 --- /dev/null +++ b/rest/application_test.go @@ -0,0 +1,199 @@ +package rest_test + +import ( + "gopkg.in/ns1/ns1-go.v2/rest/model/pulsar" + "net/http" + "testing" + + "github.com/stretchr/testify/require" + "gopkg.in/ns1/ns1-go.v2/mockns1" + api "gopkg.in/ns1/ns1-go.v2/rest" +) + +func TestApplication(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/")) + + t.Run("List", func(t *testing.T) { + t.Run("success", func(t *testing.T) { + defer mock.ClearTestCases() + client.FollowPagination = false + + applications := []*pulsar.Application{ + pulsar.NewApplication("app1"), + pulsar.NewApplication("app2"), + } + + header := http.Header{} + header.Set("Link", ``) + + require.Nil(t, mock.AddApplicationTestCase(nil, header, applications)) + + respApplications, resp, err := client.Applications.List() + require.Nil(t, err) + require.NotNil(t, respApplications) + require.Equal(t, len(applications), len(respApplications)) + require.Contains(t, resp.Header.Get("Link"), "applications?b.list.applications") + + for i := range applications { + require.Equal(t, applications[i].Name, respApplications[i].Name, 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, "/pulsar/apps", http.StatusNotFound, + nil, nil, "", `{"message": "test error"}`, + )) + + applications, resp, err := client.Applications.List() + require.Nil(t, applications) + 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("")) + applications, resp, err := c.Applications.List() + require.Nil(t, resp) + require.Error(t, err) + require.Nil(t, applications) + }) + }) + }) + + t.Run("Get", func(t *testing.T) { + id := "a32fc" + name := "MyApp" + t.Run("success", func(t *testing.T) { + defer mock.ClearTestCases() + client.FollowPagination = false + + link := `pulsar/apps/` + id + header := http.Header{} + header.Set("Link", link) + + require.Nil(t, mock.AddApplicationGetTestCase(id, + nil, + header, + pulsar.NewApplication(name))) + + respApplication, resp, err := client.Applications.Get(id) + require.Nil(t, err) + require.NotNil(t, respApplication) + require.Equal(t, name, respApplication.Name) + require.Contains(t, resp.Header.Get("Link"), link) + }) + + t.Run("Error", func(t *testing.T) { + t.Run("HTTP", func(t *testing.T) { + defer mock.ClearTestCases() + + require.Nil(t, mock.AddTestCase( + http.MethodGet, "/pulsar/apps/"+id, http.StatusNotFound, + nil, nil, "", `{"message": "test error"}`, + )) + + respApplications, resp, err := client.Applications.Get(id) + require.Nil(t, respApplications) + 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("")) + applications, resp, err := c.Applications.Get(id) + require.Nil(t, resp) + require.Error(t, err) + require.Nil(t, applications) + }) + }) + }) + + t.Run("Create", func(t *testing.T) { + application := pulsar.NewApplication("App_test") + + t.Run("Success", func(t *testing.T) { + defer mock.ClearTestCases() + + require.Nil(t, mock.AddApplicationCreateTestCase(nil, nil, application, application)) + + _, err := client.Applications.Create(application) + require.Nil(t, err) + require.Equal(t, application.Name, "App_test") + }) + + t.Run("Error", func(t *testing.T) { + defer mock.ClearTestCases() + + require.Nil(t, mock.AddTestCase( + http.MethodPut, "/pulsar/apps", http.StatusNotFound, + nil, nil, application, `{"Message": "test error"}`, + )) + + _, err := client.Applications.Create(application) + require.Contains(t, err.Error(), "test error") + }) + }) + + t.Run("Update", func(t *testing.T) { + application := pulsar.NewApplication("App_test") + application.ID = "a32fc" + + t.Run("Success", func(t *testing.T) { + defer mock.ClearTestCases() + + require.Nil(t, mock.AddApplicationUpdateTestCase(nil, + nil, + application, + application)) + + _, err := client.Applications.Update(application) + require.Nil(t, err) + }) + + t.Run("Error", func(t *testing.T) { + defer mock.ClearTestCases() + + require.Nil(t, mock.AddTestCase( + http.MethodPost, "/pulsar/apps/"+application.ID, http.StatusNotFound, + nil, nil, application, `{"message": "pulsar app not found"}`, + )) + + _, err := client.Applications.Update(application) + require.Equal(t, api.ErrApplicationMissing, err) + }) + }) + + t.Run("Delete", func(t *testing.T) { + id := "a32fc1" + t.Run("Success", func(t *testing.T) { + defer mock.ClearTestCases() + + require.Nil(t, mock.AddApplicationDeleteTestCase(id, nil, nil)) + + _, err := client.Applications.Delete(id) + require.Nil(t, err) + }) + + t.Run("Error", func(t *testing.T) { + defer mock.ClearTestCases() + + require.Nil(t, mock.AddTestCase( + http.MethodDelete, "/pulsar/apps/"+ id, http.StatusNotFound, + nil, nil, "", `{"message": "pulsar app not found"}`, + )) + + _, err := client.Applications.Delete(id) + require.Equal(t, api.ErrApplicationMissing, err) + }) + }) +} diff --git a/rest/model/pulsar/application_test.go b/rest/model/pulsar/application_test.go new file mode 100644 index 0000000..adbab38 --- /dev/null +++ b/rest/model/pulsar/application_test.go @@ -0,0 +1,11 @@ +package pulsar + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestNewBBPulsarJob(t *testing.T) { + a := NewApplication("app_name") + assert.Equal(t, "app_name", a.Name, "Wrong name") +} \ No newline at end of file From 6975999a9497ebfd1200d96d4b4606eafd990c0e Mon Sep 17 00:00:00 2001 From: Pedro Angelo Date: Fri, 20 Aug 2021 11:17:44 -0300 Subject: [PATCH 3/6] added applications rest example --- rest/_examples/applications.go | 125 ++++++++++++++++++++++++++ rest/model/pulsar/application_test.go | 2 +- 2 files changed, 126 insertions(+), 1 deletion(-) create mode 100644 rest/_examples/applications.go diff --git a/rest/_examples/applications.go b/rest/_examples/applications.go new file mode 100644 index 0000000..2c6d4a9 --- /dev/null +++ b/rest/_examples/applications.go @@ -0,0 +1,125 @@ +package main + +import ( + "fmt" + "gopkg.in/ns1/ns1-go.v2/rest/model/pulsar" + "log" + "net/http" + "os" + "strconv" + "time" + + api "gopkg.in/ns1/ns1-go.v2/rest" +) + +var client *api.Client + +// Helper that initializes rest api client from environment variable. +func init() { + k := os.Getenv("NS1_APIKEY") + if k == "" { + fmt.Println("NS1_APIKEY environment variable is not set, giving up") + } + + httpClient := &http.Client{Timeout: time.Second * 10} + // Adds logging to each http request. + doer := api.Decorate(httpClient, api.Logging(log.New(os.Stdout, "", log.LstdFlags))) + client = api.NewClient(doer, api.SetAPIKey(k)) +} + +func main() { + // create applications + var app *pulsar.Application + var apps []*pulsar.Application + for i := 0; i < 2; i++ { + app = pulsar.NewApplication("MyAPP_" + strconv.Itoa(i)) + app.DefaultConfig = pulsar.DefaultConfig{ + Http: false, + Https: false, + RequestTimeoutMillis: 100 + 10*i, + JobTimeoutMillis: 100 + 10*i, + UseXhr: false, + StaticValues: false, + } + app.Active = false + app.BrowserWaitMillis = 100 + app.JobsPerTransaction = 100 + + _, err := client.Applications.Create(app) + if err != nil { + panic("could not create " + app.Name) + return + } + apps = append(apps, app) + } + + // list all + err := printAllApplications() + if err != nil { + return + } + + // print all by id + err = printAllByID(apps) + if err != nil { + return + } + + // update + for i, v := range apps { + v.Name = "MyAPP_Updated_" + strconv.Itoa(i) + _, err = client.Applications.Update(v) + + if err != nil { + panic("could not get application" + err.Error()) + return + } + } + + // print all by id + err = printAllByID(apps) + if err != nil { + return + } + + //delete all + for _, v := range apps { + _, err := client.Applications.Delete(v.ID) + if err != nil { + panic("could not delete " + v.ID + " " + err.Error()) + return + } + } +} + +func printAllApplications() error { + appsList, _, err := client.Applications.List() + fmt.Println("\n#### printing all ####") + if err != nil { + panic("could not list all applications " + err.Error()) + return err + } + for _, v := range appsList { + fmt.Println("%w", v) + } + fmt.Println("") + return nil +} + +func printAllByID(apps []*pulsar.Application) error { + var appsReturned []*pulsar.Application + for _, v := range apps { + app, _, err := client.Applications.Get(v.ID) + if err != nil { + panic("could not get application" + err.Error()) + return err + } + appsReturned = append(appsReturned, app) + } + fmt.Println("\n#### printing all by id ####") + for _, v := range appsReturned { + fmt.Println("%w", v) + } + fmt.Println("") + return nil +} diff --git a/rest/model/pulsar/application_test.go b/rest/model/pulsar/application_test.go index adbab38..1f59d95 100644 --- a/rest/model/pulsar/application_test.go +++ b/rest/model/pulsar/application_test.go @@ -8,4 +8,4 @@ import ( func TestNewBBPulsarJob(t *testing.T) { a := NewApplication("app_name") assert.Equal(t, "app_name", a.Name, "Wrong name") -} \ No newline at end of file +} From e799b54bc642d1557e2f5913b7a3123c3fc7db40 Mon Sep 17 00:00:00 2001 From: Pedro Angelo Date: Fri, 27 Aug 2021 11:50:19 -0300 Subject: [PATCH 4/6] chages requested on code review --- rest/application.go | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/rest/application.go b/rest/application.go index 3926717..1d895e6 100644 --- a/rest/application.go +++ b/rest/application.go @@ -20,9 +20,8 @@ func (s *ApplicationsService) List() ([]*pulsar.Application, *http.Response, err return nil, nil, err } - al := []*pulsar.Application{} - var resp *http.Response - resp, err = s.client.Do(req, &al) + var al []*pulsar.Application + resp, err := s.client.Do(req, &al) if err != nil { return nil, resp, err } @@ -43,11 +42,10 @@ func (s *ApplicationsService) Get(id string) (*pulsar.Application, *http.Respons var a pulsar.Application resp, err := s.client.Do(req, &a) - if err != nil { switch err.(type) { case *Error: - if err.(*Error).Message == "pulsar app not found" { + if err.(*Error).Resp.StatusCode == 404 { return nil, resp, ErrApplicationMissing } } @@ -62,11 +60,11 @@ func (s *ApplicationsService) Get(id string) (*pulsar.Application, *http.Respons // The given application must have at least the name // NS1 API docs: https://ns1.com/api#put-create-a-pulsar-application func (s *ApplicationsService) Create(a *pulsar.Application) (*http.Response, error) { - req, err := s.client.NewRequest("PUT", "pulsar/apps", &a) + req, err := s.client.NewRequest("PUT", "pulsar/apps", a) if err != nil { return nil, err } - resp, err := s.client.Do(req, &a) + resp, err := s.client.Do(req, a) return resp, err } @@ -82,7 +80,6 @@ func (s *ApplicationsService) Update(a *pulsar.Application) (*http.Response, err } resp, err := s.client.Do(req, &a) - if err != nil { switch err.(type) { case *Error: From 28589cd90d5f3d2da38c2376c716fa6e5151b8a9 Mon Sep 17 00:00:00 2001 From: Pedro Angelo Date: Tue, 31 Aug 2021 15:52:06 -0300 Subject: [PATCH 5/6] small adjusts --- rest/application.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rest/application.go b/rest/application.go index 1d895e6..0aa4eb2 100644 --- a/rest/application.go +++ b/rest/application.go @@ -83,7 +83,7 @@ func (s *ApplicationsService) Update(a *pulsar.Application) (*http.Response, err if err != nil { switch err.(type) { case *Error: - if err.(*Error).Message == "pulsar app not found" { + if err.(*Error).Resp.StatusCode == 404 { return resp, ErrApplicationMissing } } @@ -108,7 +108,7 @@ func (s *ApplicationsService) Delete(id string) (*http.Response, error) { if err != nil { switch err.(type) { case *Error: - if err.(*Error).Message == "pulsar app not found" { + if err.(*Error).Resp.StatusCode == 404 { return resp, ErrApplicationMissing } } From 06880462f438c1f63d4dd669077c0b02672413a4 Mon Sep 17 00:00:00 2001 From: Pedro Angelo Date: Tue, 31 Aug 2021 16:10:28 -0300 Subject: [PATCH 6/6] fix typo --- mockns1/{applicaiton.go => application.go} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename mockns1/{applicaiton.go => application.go} (100%) diff --git a/mockns1/applicaiton.go b/mockns1/application.go similarity index 100% rename from mockns1/applicaiton.go rename to mockns1/application.go