github.com/saucelabs/saucectl@v0.175.1/internal/http/apitester_test.go (about)

     1  package http
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"net/http"
     8  	"net/http/httptest"
     9  	"reflect"
    10  	"testing"
    11  	"time"
    12  
    13  	"github.com/google/go-cmp/cmp"
    14  	"github.com/hashicorp/go-retryablehttp"
    15  	"github.com/saucelabs/saucectl/internal/apitest"
    16  	"github.com/saucelabs/saucectl/internal/config"
    17  	"github.com/stretchr/testify/assert"
    18  	"golang.org/x/time/rate"
    19  )
    20  
    21  func createTestRetryableHTTPClient(t *testing.T) *retryablehttp.Client {
    22  	return &retryablehttp.Client{
    23  		HTTPClient: &http.Client{
    24  			Timeout:   10 * time.Second,
    25  			Transport: &http.Transport{Proxy: http.ProxyFromEnvironment},
    26  		},
    27  		RetryWaitMin: 0 * time.Second,
    28  		RetryWaitMax: 0 * time.Second,
    29  		RetryMax:     1,
    30  		CheckRetry:   retryablehttp.DefaultRetryPolicy,
    31  		Backoff:      retryablehttp.DefaultBackoff,
    32  		ErrorHandler: retryablehttp.PassthroughErrorHandler,
    33  	}
    34  }
    35  
    36  func TestAPITester_GetEventResult(t *testing.T) {
    37  	type args struct {
    38  		ctx     context.Context
    39  		hookID  string
    40  		eventID string
    41  	}
    42  	tests := []struct {
    43  		name    string
    44  		args    args
    45  		want    apitest.TestResult
    46  		wantErr bool
    47  	}{
    48  		{
    49  			name: "passing test",
    50  			args: args{
    51  				hookID:  "dummyHookId",
    52  				eventID: "completedEvent",
    53  				ctx:     context.Background(),
    54  			},
    55  			want: apitest.TestResult{
    56  				EventID:              "638e1e14a1da1e511c776eea",
    57  				ExecutionTimeSeconds: 31,
    58  				Async:                false,
    59  				FailuresCount:        0,
    60  				Project: apitest.ProjectMeta{
    61  					ID:   "6244d915ca28694aab958bbe",
    62  					Name: "Test Project",
    63  				},
    64  				Test: apitest.Test{
    65  					ID:   "638788b12d29c47170999eee",
    66  					Name: "test_demo",
    67  				},
    68  			},
    69  			wantErr: false,
    70  		},
    71  		{
    72  			name: "404 Event",
    73  			args: args{
    74  				hookID:  "dummyHookId",
    75  				eventID: "incompleteEvent",
    76  				ctx:     context.Background(),
    77  			},
    78  			wantErr: true,
    79  		},
    80  		{
    81  			name: "Buggy Event",
    82  			args: args{
    83  				hookID:  "dummyHookId",
    84  				eventID: "buggyEvent",
    85  				ctx:     context.Background(),
    86  			},
    87  			wantErr: true,
    88  		},
    89  	}
    90  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    91  		var err error
    92  		switch r.URL.Path {
    93  		case "/api-testing/rest/v4/dummyHookId/insights/events/completedEvent":
    94  			completeStatusResp := []byte(`{"_id":"638e1e14a1da1e511c776eea","events":[],"tags":["canfail"],"criticalFailures":[],"httpFailures":[],"facts":{},"date":1670258196613,"test":{"name":"test_demo","id":"638788b12d29c47170999eee"},"failuresCount":0,"warningsCount":0,"compressed":false,"run":{"name":"","id":""},"company":{"name":"","id":"7fb25570b4064716b9b6daae1a997bba"},"project":{"name":"Test Project","id":"6244d915ca28694aab958bbe"},"temp":false,"expireAt":"2023-06-06T04:37:07Z","executionTimeSeconds":31,"taskId":"ad24fdd6-8e47-401c-81ce-866553194bdd","agent":"wstestjs","mode":"ondemand","buildId":"Test","clientname":"","initiator":{"name":"Incitator","id":"de8691a22ff343f08aa6fb63e485fe0d","teamid":"0205cb60678a4372193bac4052c048be"}}`)
    95  			_, err = w.Write(completeStatusResp)
    96  		case "/api-testing/rest/v4/dummyHookId/insights/events/incompleteEvent":
    97  			errorStatusResp := []byte(`{"status": "error","message": "event not found"}`)
    98  			w.WriteHeader(http.StatusNotFound)
    99  			_, err = w.Write(errorStatusResp)
   100  		case "/api-testing/rest/v4/dummyHookId/insights/events/unauthorized":
   101  			w.WriteHeader(http.StatusUnauthorized)
   102  		default:
   103  			w.WriteHeader(http.StatusInternalServerError)
   104  		}
   105  
   106  		if err != nil {
   107  			t.Errorf("failed to respond: %v", err)
   108  		}
   109  	}))
   110  	defer ts.Close()
   111  
   112  	c := &APITester{
   113  		HTTPClient:         createTestRetryableHTTPClient(t),
   114  		URL:                ts.URL,
   115  		Username:           "dummy",
   116  		AccessKey:          "accesskey",
   117  		RequestRateLimiter: rate.NewLimiter(rate.Inf, 0),
   118  	}
   119  	for _, tt := range tests {
   120  		t.Run(tt.name, func(t *testing.T) {
   121  			got, err := c.GetEventResult(tt.args.ctx, tt.args.hookID, tt.args.eventID)
   122  			if (err != nil) != tt.wantErr {
   123  				t.Errorf("GetEventResult() error = %v, wantErr %v", err, tt.wantErr)
   124  				return
   125  			}
   126  			if !cmp.Equal(got, tt.want) {
   127  				t.Errorf("GetEventResult() got = %v, want %v", got, tt.want)
   128  			}
   129  		})
   130  	}
   131  }
   132  
   133  func TestAPITester_GetProject(t *testing.T) {
   134  	type args struct {
   135  		ctx    context.Context
   136  		hookID string
   137  	}
   138  	tests := []struct {
   139  		name    string
   140  		args    args
   141  		want    apitest.ProjectMeta
   142  		wantErr bool
   143  	}{
   144  		{
   145  			name: "Passing Project Fetch",
   146  			args: args{ctx: context.Background(), hookID: "dummyProject"},
   147  			want: apitest.ProjectMeta{
   148  				ID:   "6244d915ca28694aab000000",
   149  				Name: "Test Project",
   150  			},
   151  			wantErr: false,
   152  		},
   153  		{
   154  			name:    "Failing Project Fetch",
   155  			args:    args{ctx: context.Background(), hookID: "nonExistingProject"},
   156  			wantErr: true,
   157  		},
   158  	}
   159  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   160  		var err error
   161  		switch r.URL.Path {
   162  		case "/api-testing/rest/v4/dummyProject":
   163  			completeStatusResp := []byte(`{"id":"6244d915ca28694aab000000","name":"Test Project","teamId":"0205cb60678a4372b9ee20408725467ad","description":"","tags":[],"notes":"","type":"project","emailNotifications":[],"connectorNotifications":[]}`)
   164  			_, err = w.Write(completeStatusResp)
   165  		case "/api-testing/rest/v4/nonExistingProject":
   166  			w.WriteHeader(http.StatusNotFound)
   167  		default:
   168  			w.WriteHeader(http.StatusInternalServerError)
   169  		}
   170  
   171  		if err != nil {
   172  			t.Errorf("failed to respond: %v", err)
   173  		}
   174  	}))
   175  	defer ts.Close()
   176  	c := &APITester{
   177  		HTTPClient: createTestRetryableHTTPClient(t),
   178  		URL:        ts.URL,
   179  		Username:   "dummy",
   180  		AccessKey:  "accesskey",
   181  	}
   182  
   183  	for _, tt := range tests {
   184  		t.Run(tt.name, func(t *testing.T) {
   185  			got, err := c.GetProject(tt.args.ctx, tt.args.hookID)
   186  			if (err != nil) != tt.wantErr {
   187  				t.Errorf("GetProject() error = %v, wantErr %v", err, tt.wantErr)
   188  				return
   189  			}
   190  			if !cmp.Equal(got, tt.want) {
   191  				t.Errorf("GetProject() got = %v, want %v", got, tt.want)
   192  			}
   193  		})
   194  	}
   195  }
   196  
   197  func TestAPITester_GetTest(t *testing.T) {
   198  	type args struct {
   199  		ctx    context.Context
   200  		hookID string
   201  		testID string
   202  	}
   203  	tests := []struct {
   204  		name    string
   205  		args    args
   206  		want    apitest.Test
   207  		wantErr bool
   208  	}{
   209  		{
   210  			name: "Passing Test Fetch",
   211  			args: args{ctx: context.Background(), hookID: "dummyProject", testID: "existingTest"},
   212  			want: apitest.Test{
   213  				ID:   "638788b12d29c47170d20db4",
   214  				Name: "test_cli",
   215  			},
   216  			wantErr: false,
   217  		},
   218  		{
   219  			name:    "Failing test fetch",
   220  			args:    args{ctx: context.Background(), hookID: "dummyProject", testID: "nonexistentTest"},
   221  			wantErr: true,
   222  		},
   223  	}
   224  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   225  		var err error
   226  		switch r.URL.Path {
   227  		case "/api-testing/rest/v4/dummyProject/tests/existingTest":
   228  			completeStatusResp := []byte(`{"published":{"id":"638788b12d29c47170d20db4","name":"test_cli","description":"","lastModified":"2022-11-30T17:20:08Z","tags":["canfail"],"user":{"id":"de8691a22ff343f08aa6fb63e963121d","name":"Username"},"unit":"assertions:\n  - id: get\n    children:\n      - id: header\n        name: x-rapidmock-delay\n        value: \"10000\"\n    url: https://api.rapidmock.com/mocks/f6GeB\n    var: payload\n    mode: json\n  - id: if\n    children:\n      - id: comment\n        text: endpoint is not working fine, test will be stopped\n      - id: flow\n        command: stop\n    expression: payload_response.statusCode!='200'\nconfigs: []","input":"- id: global\n  children:\n    - id: variable\n      name: protocol\n      value: http://\n    - id: variable\n      name: domain\n      value: demoapi.apifortress.com\n    - id: variable\n      name: endpoint\n      value: /api/retail/product/${id}\n    - id: variable\n      name: auth\n      value: ABC123\n- id: sets\n  children:\n    - id: set\n      children:\n        - id: variable\n          name: id\n          value: \"1\"\n      name: product 1\n    - id: set\n      children:\n        - id: variable\n          name: id\n          value: \"4\"\n      name: product 2\n    - id: set\n      children:\n        - id: variable\n          name: id\n          value: \"7\"\n      name: product 3","complete":true},"workingCopy":{"id":"638790c8e90a3c46b5c83a98","user":{"id":"de8691a22ff343f08aa6fb63e963121d","name":"Username"},"unit":"assertions:\n  - id: get\n    children:\n      - id: header\n        name: x-rapidmock-delay\n        value: \"10000\"\n    url: https://api.rapidmock.com/mocks/f6GeB\n    var: payload\n    mode: json\n  - id: if\n    children:\n      - id: comment\n        text: endpoint is not working fine, test will be stopped\n      - id: flow\n        command: stop\n    expression: payload_response.statusCode!='200'\nconfigs: []","input":"- id: global\n  children:\n    - id: variable\n      name: protocol\n      value: http://\n    - id: variable\n      name: domain\n      value: demoapi.apifortress.com\n    - id: variable\n      name: endpoint\n      value: /api/retail/product/${id}\n    - id: variable\n      name: auth\n      value: ABC123\n- id: sets\n  children:\n    - id: set\n      children:\n        - id: variable\n          name: id\n          value: \"1\"\n      name: product 1\n    - id: set\n      children:\n        - id: variable\n          name: id\n          value: \"4\"\n      name: product 2\n    - id: set\n      children:\n        - id: variable\n          name: id\n          value: \"7\"\n      name: product 3","lastModified":"2022-11-30T17:20:08Z"}}`)
   229  			_, err = w.Write(completeStatusResp)
   230  		case "/api-testing/rest/v4/dummyProject/tests/nonexistentTest":
   231  			w.WriteHeader(http.StatusNotFound)
   232  		default:
   233  			w.WriteHeader(http.StatusInternalServerError)
   234  		}
   235  
   236  		if err != nil {
   237  			t.Errorf("failed to respond: %v", err)
   238  		}
   239  	}))
   240  	defer ts.Close()
   241  	c := &APITester{
   242  		HTTPClient: createTestRetryableHTTPClient(t),
   243  		URL:        ts.URL,
   244  		Username:   "dummy",
   245  		AccessKey:  "accesskey",
   246  	}
   247  
   248  	for _, tt := range tests {
   249  		t.Run(tt.name, func(t *testing.T) {
   250  			got, err := c.GetTest(tt.args.ctx, tt.args.hookID, tt.args.testID)
   251  			if (err != nil) != tt.wantErr {
   252  				t.Errorf("GetProject() error = %v, wantErr %v", err, tt.wantErr)
   253  				return
   254  			}
   255  			if !cmp.Equal(got, tt.want) {
   256  				t.Errorf("GetProject() got = %v, want %v", got, tt.want)
   257  			}
   258  		})
   259  	}
   260  }
   261  
   262  func TestAPITester_composeURL(t *testing.T) {
   263  	type args struct {
   264  		path    string
   265  		buildID string
   266  		format  string
   267  		tunnel  config.Tunnel
   268  		taskID  string
   269  	}
   270  	tests := []struct {
   271  		name string
   272  		args args
   273  		want string
   274  	}{
   275  		{
   276  			name: "Default Path",
   277  			args: args{
   278  				path: "/dummy/path",
   279  			},
   280  			want: "/dummy/path",
   281  		},
   282  		{
   283  			name: "Path with buildId",
   284  			args: args{
   285  				path:    "/dummy/path",
   286  				buildID: "buildId",
   287  			},
   288  			want: "/dummy/path?buildId=buildId",
   289  		},
   290  		{
   291  			name: "Path with buildId and Format",
   292  			args: args{
   293  				path:    "/dummy/path",
   294  				buildID: "buildId",
   295  				format:  "json",
   296  			},
   297  			want: "/dummy/path?buildId=buildId&format=json",
   298  		},
   299  		{
   300  			name: "Path with Format",
   301  			args: args{
   302  				path:   "/dummy/path",
   303  				format: "json",
   304  			},
   305  			want: "/dummy/path?format=json",
   306  		},
   307  		{
   308  			name: "Path with tunnel with owner",
   309  			args: args{
   310  				path: "/dummy/path",
   311  				tunnel: config.Tunnel{
   312  					Name:  "tunnelId",
   313  					Owner: "tunnelOwner",
   314  				},
   315  			},
   316  			want: "/dummy/path?tunnelId=tunnelOwner%3AtunnelId",
   317  		},
   318  		{
   319  			name: "Path with tunnel without owner",
   320  			args: args{
   321  				path: "/dummy/path",
   322  				tunnel: config.Tunnel{
   323  					Name: "tunnelId",
   324  				},
   325  			},
   326  			want: "/dummy/path?tunnelId=dummyUsername%3AtunnelId",
   327  		},
   328  		{
   329  			name: "Path with taskId",
   330  			args: args{
   331  				path:   "/dummy/path",
   332  				taskID: "taskId",
   333  			},
   334  			want: "/dummy/path?taskId=taskId",
   335  		},
   336  	}
   337  	c := &APITester{
   338  		Username: "dummyUsername",
   339  	}
   340  	for _, tt := range tests {
   341  		t.Run(tt.name, func(t *testing.T) {
   342  			if got := c.composeURL(tt.args.path, tt.args.buildID, tt.args.format, tt.args.tunnel, tt.args.taskID); got != tt.want {
   343  				t.Errorf("composeURL() = %v, want %v", got, tt.want)
   344  			}
   345  		})
   346  	}
   347  }
   348  
   349  func TestAPITester_GetProjects(t *testing.T) {
   350  	tests := []struct {
   351  		name    string
   352  		want    []apitest.ProjectMeta
   353  		wantErr assert.ErrorAssertionFunc
   354  	}{
   355  		{
   356  			name: "Fetching Projects Test",
   357  			wantErr: func(t assert.TestingT, err error, i ...interface{}) bool {
   358  				return err != nil
   359  			},
   360  			want: []apitest.ProjectMeta{},
   361  		},
   362  	}
   363  
   364  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   365  		var err error
   366  		switch r.URL.Path {
   367  		case "/api-testing/api/project":
   368  			completeStatusResp := []byte(`[{"id":"63dbe9d6f48c8412fe79220d","name":"Demo Project","teamId":null,"description":"","tags":[],"notes":"","type":"project","emailNotifications":[],"connectorNotifications":[]}]`)
   369  			_, err = w.Write(completeStatusResp)
   370  		default:
   371  			w.WriteHeader(http.StatusInternalServerError)
   372  		}
   373  
   374  		if err != nil {
   375  			t.Errorf("failed to respond: %v", err)
   376  		}
   377  	}))
   378  	defer ts.Close()
   379  
   380  	c := &APITester{
   381  		HTTPClient: createTestRetryableHTTPClient(t),
   382  		URL:        ts.URL,
   383  		Username:   "dummy",
   384  		AccessKey:  "accesskey",
   385  	}
   386  
   387  	for _, tt := range tests {
   388  		t.Run(tt.name, func(t *testing.T) {
   389  			got, err := c.GetProjects(context.Background())
   390  			if !tt.wantErr(t, err, fmt.Sprintf("GetProjects(%v)", context.Background())) {
   391  				return
   392  			}
   393  			assert.Equalf(t, tt.want, got, "GetProjects(%v)", context.Background())
   394  		})
   395  	}
   396  }
   397  
   398  func TestAPITester_GetHooks(t *testing.T) {
   399  	type params struct {
   400  		projectID string
   401  	}
   402  
   403  	tests := []struct {
   404  		name    string
   405  		params  params
   406  		want    []apitest.Hook
   407  		wantErr error
   408  	}{
   409  		{
   410  			name: "Projects with no hooks",
   411  			params: params{
   412  				projectID: "noHooks",
   413  			},
   414  			wantErr: nil,
   415  			want:    []apitest.Hook{},
   416  		},
   417  		{
   418  			name: "Projects with multiple hooks",
   419  			params: params{
   420  				projectID: "multipleHooks",
   421  			},
   422  			wantErr: nil,
   423  			want: []apitest.Hook{
   424  				{
   425  					Identifier: "e291c7c5-d091-4bae-8293-7315fc15cc4c",
   426  					Name:       "name1",
   427  				},
   428  				{
   429  					Identifier: "4d66f4d0-a29a-43a1-a787-94f7b8cc2e21",
   430  					Name:       "name2",
   431  				},
   432  			},
   433  		},
   434  		{
   435  			name: "Invalid Project",
   436  			params: params{
   437  				projectID: "invalidProject",
   438  			},
   439  			wantErr: errors.New(`request failed; unexpected response code:'404', msg:'{"status":"error","message":"Not Found"}'`),
   440  			want:    []apitest.Hook{},
   441  		},
   442  	}
   443  
   444  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   445  		var err error
   446  		switch r.URL.Path {
   447  		case "/api-testing/api/project/noHooks/hook":
   448  			completeStatusResp := []byte(`[]`)
   449  			_, err = w.Write(completeStatusResp)
   450  		case "/api-testing/api/project/multipleHooks/hook":
   451  			completeStatusResp := []byte(`[{"id":"hook1","identifier":"e291c7c5-d091-4bae-8293-7315fc15cc4c","name":"name1","description":"description1"},{"id":"hook2","identifier":"4d66f4d0-a29a-43a1-a787-94f7b8cc2e21","name":"name2","description":"description2"}]`)
   452  			_, err = w.Write(completeStatusResp)
   453  		case "/api-testing/api/project/invalidProject/hook":
   454  			completeStatusResp := []byte(`{"status":"error","message":"Not Found"}`)
   455  			w.WriteHeader(http.StatusNotFound)
   456  			_, err = w.Write(completeStatusResp)
   457  		default:
   458  			w.WriteHeader(http.StatusInternalServerError)
   459  		}
   460  
   461  		if err != nil {
   462  			t.Errorf("failed to respond: %v", err)
   463  		}
   464  	}))
   465  	defer ts.Close()
   466  
   467  	c := &APITester{
   468  		HTTPClient: createTestRetryableHTTPClient(t),
   469  		URL:        ts.URL,
   470  		Username:   "dummy",
   471  		AccessKey:  "accesskey",
   472  	}
   473  
   474  	for _, tt := range tests {
   475  		t.Run(tt.name, func(t *testing.T) {
   476  			got, err := c.GetHooks(context.Background(), tt.params.projectID)
   477  			if !reflect.DeepEqual(err, tt.wantErr) {
   478  				t.Errorf("GetHooks(%v, %s): got %v want %v", context.Background(), tt.params.projectID, err, tt.wantErr)
   479  				return
   480  			}
   481  			assert.Equalf(t, tt.want, got, "GetHooks(%v, %s)", context.Background(), tt.params.projectID)
   482  		})
   483  	}
   484  }
   485  
   486  func TestAPITester_RunAllAsync(t *testing.T) {
   487  	type args struct {
   488  		ctx     context.Context
   489  		hookID  string
   490  		buildID string
   491  		tunnel  config.Tunnel
   492  	}
   493  	tests := []struct {
   494  		name    string
   495  		args    args
   496  		want    apitest.AsyncResponse
   497  		wantErr bool
   498  	}{
   499  		{
   500  			name: "Basic trigger",
   501  			args: args{
   502  				ctx:    context.Background(),
   503  				hookID: "dummyHookId",
   504  			},
   505  			want: apitest.AsyncResponse{
   506  				ContextIDs: []string{"221270ac-0229-49d1-9025-251a10e9133d"},
   507  				EventIDs:   []string{"c4ca4238a0b923820dcc509a"},
   508  				TaskID:     "6ddf80b7-9753-4802-992b-d42948cdb99f",
   509  				TestIDs:    []string{"c20ad4d76fe97759aa27a0c9"},
   510  			},
   511  		},
   512  	}
   513  
   514  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   515  		var err error
   516  		if r.Method != http.MethodPost {
   517  			w.WriteHeader(http.StatusNotImplemented)
   518  			return
   519  		}
   520  		switch r.URL.Path {
   521  		case "/api-testing/rest/v4/dummyHookId/tests/_run-all":
   522  			completeStatusResp := []byte(`{"contextIds":["221270ac-0229-49d1-9025-251a10e9133d"],"eventIds":["c4ca4238a0b923820dcc509a"],"taskId":"6ddf80b7-9753-4802-992b-d42948cdb99f","testIds":["c20ad4d76fe97759aa27a0c9"]}`)
   523  			_, err = w.Write(completeStatusResp)
   524  		default:
   525  			w.WriteHeader(http.StatusInternalServerError)
   526  		}
   527  
   528  		if err != nil {
   529  			t.Errorf("failed to respond: %v", err)
   530  		}
   531  	}))
   532  	defer ts.Close()
   533  	c := &APITester{
   534  		HTTPClient: createTestRetryableHTTPClient(t),
   535  		URL:        ts.URL,
   536  		Username:   "dummyUser",
   537  		AccessKey:  "dummyAccesKey",
   538  	}
   539  
   540  	for _, tt := range tests {
   541  		t.Run(tt.name, func(t *testing.T) {
   542  			got, err := c.RunAllAsync(tt.args.ctx, tt.args.hookID, tt.args.buildID, tt.args.tunnel, apitest.TestRequest{})
   543  			if (err != nil) != tt.wantErr {
   544  				t.Errorf("RunAllAsync() error = %v, wantErr %v", err, tt.wantErr)
   545  				return
   546  			}
   547  			if !cmp.Equal(got, tt.want) {
   548  				t.Errorf("RunAllAsync() got = %v, want %v", got, tt.want)
   549  			}
   550  		})
   551  	}
   552  }
   553  
   554  func TestAPITester_RunEphemeralAsync(t *testing.T) {
   555  	type args struct {
   556  		ctx     context.Context
   557  		hookID  string
   558  		buildID string
   559  		tunnel  config.Tunnel
   560  		taskID  string
   561  		test    apitest.TestRequest
   562  	}
   563  	tests := []struct {
   564  		name          string
   565  		args          args
   566  		assertRequest func(t *testing.T, r *http.Request)
   567  		reply         []byte
   568  		want          apitest.AsyncResponse
   569  		wantErr       bool
   570  	}{
   571  		{
   572  			name: "Complete Trigger",
   573  			args: args{
   574  				ctx:     context.Background(),
   575  				hookID:  "dummyHookId",
   576  				taskID:  "generatedUuid",
   577  				buildID: "generatedBuildId",
   578  				tunnel:  config.Tunnel{Name: "tunnelId"},
   579  				test:    apitest.TestRequest{},
   580  			},
   581  			assertRequest: func(t *testing.T, r *http.Request) {
   582  				assert.Equal(t, "/api-testing/rest/v4/dummyHookId/tests/_exec?buildId=generatedBuildId&tunnelId=dummyUser%3AtunnelId", r.RequestURI)
   583  			},
   584  			reply: []byte(`{"contextIds":["221270ac-0229-49d1-9025-251a10e9133d"],"eventIds":["c4ca4238a0b923820dcc509a"],"taskId":"6ddf80b7-9753-4802-992b-d42948cdb99f","testIds":["c20ad4d76fe97759aa27a0c9"]}`),
   585  			want: apitest.AsyncResponse{
   586  				ContextIDs: []string{"221270ac-0229-49d1-9025-251a10e9133d"},
   587  				EventIDs:   []string{"c4ca4238a0b923820dcc509a"},
   588  				TaskID:     "6ddf80b7-9753-4802-992b-d42948cdb99f",
   589  				TestIDs:    []string{"c20ad4d76fe97759aa27a0c9"},
   590  			},
   591  		},
   592  	}
   593  
   594  	for _, tt := range tests {
   595  		t.Run(tt.name, func(t *testing.T) {
   596  			ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   597  				if r.Method != http.MethodPost {
   598  					w.WriteHeader(http.StatusNotImplemented)
   599  					return
   600  				}
   601  				switch r.URL.Path {
   602  				case "/api-testing/rest/v4/dummyHookId/tests/_exec":
   603  					tt.assertRequest(t, r)
   604  					completeStatusResp := tt.reply
   605  					_, _ = w.Write(completeStatusResp)
   606  				default:
   607  					w.WriteHeader(http.StatusInternalServerError)
   608  				}
   609  			}))
   610  			defer ts.Close()
   611  			c := &APITester{
   612  				HTTPClient: createTestRetryableHTTPClient(t),
   613  				URL:        ts.URL,
   614  				Username:   "dummyUser",
   615  				AccessKey:  "dummyAccesKey",
   616  			}
   617  
   618  			got, err := c.RunEphemeralAsync(tt.args.ctx, tt.args.hookID, tt.args.buildID, tt.args.tunnel, tt.args.taskID, tt.args.test)
   619  			if (err != nil) != tt.wantErr {
   620  				t.Errorf("RunAllAsync() error = %v, wantErr %v", err, tt.wantErr)
   621  				return
   622  			}
   623  			if !cmp.Equal(got, tt.want) {
   624  				t.Errorf("RunAllAsync() got = %v, want %v", got, tt.want)
   625  			}
   626  		})
   627  	}
   628  }
   629  
   630  func TestAPITester_RunTestAsync(t *testing.T) {
   631  	type args struct {
   632  		ctx     context.Context
   633  		testID  string
   634  		hookID  string
   635  		buildID string
   636  		tunnel  config.Tunnel
   637  	}
   638  	tests := []struct {
   639  		name    string
   640  		args    args
   641  		want    apitest.AsyncResponse
   642  		wantErr bool
   643  	}{
   644  		{
   645  			name: "Basic trigger",
   646  			args: args{
   647  				ctx:    context.Background(),
   648  				hookID: "dummyHookId",
   649  				testID: "c20ad4d76fe97759aa27a0c9",
   650  			},
   651  			want: apitest.AsyncResponse{
   652  				ContextIDs: []string{"221270ac-0229-49d1-9025-251a10e9133d"},
   653  				EventIDs:   []string{"c4ca4238a0b923820dcc509a"},
   654  				TaskID:     "6ddf80b7-9753-4802-992b-d42948cdb99f",
   655  				TestIDs:    []string{"c20ad4d76fe97759aa27a0c9"},
   656  			},
   657  		},
   658  	}
   659  
   660  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   661  		var err error
   662  		if r.Method != http.MethodPost {
   663  			w.WriteHeader(http.StatusNotImplemented)
   664  			return
   665  		}
   666  		switch r.URL.Path {
   667  		case "/api-testing/rest/v4/dummyHookId/tests/c20ad4d76fe97759aa27a0c9/_run":
   668  			completeStatusResp := []byte(`{"contextIds":["221270ac-0229-49d1-9025-251a10e9133d"],"eventIds":["c4ca4238a0b923820dcc509a"],"taskId":"6ddf80b7-9753-4802-992b-d42948cdb99f","testIds":["c20ad4d76fe97759aa27a0c9"]}`)
   669  			_, err = w.Write(completeStatusResp)
   670  		default:
   671  			w.WriteHeader(http.StatusInternalServerError)
   672  		}
   673  
   674  		if err != nil {
   675  			t.Errorf("failed to respond: %v", err)
   676  		}
   677  	}))
   678  	defer ts.Close()
   679  	c := &APITester{
   680  		HTTPClient: createTestRetryableHTTPClient(t),
   681  		URL:        ts.URL,
   682  		Username:   "dummyUser",
   683  		AccessKey:  "dummyAccesKey",
   684  	}
   685  
   686  	for _, tt := range tests {
   687  		t.Run(tt.name, func(t *testing.T) {
   688  			got, err := c.RunTestAsync(tt.args.ctx, tt.args.hookID, tt.args.testID, tt.args.buildID, tt.args.tunnel, apitest.TestRequest{})
   689  			if (err != nil) != tt.wantErr {
   690  				t.Errorf("RunAllAsync() error = %v, wantErr %v", err, tt.wantErr)
   691  				return
   692  			}
   693  			if !cmp.Equal(got, tt.want) {
   694  				t.Errorf("RunAllAsync() got = %v, want %v", got, tt.want)
   695  			}
   696  		})
   697  	}
   698  }
   699  
   700  func TestAPITester_RunTagAsync(t *testing.T) {
   701  	type args struct {
   702  		ctx     context.Context
   703  		hookID  string
   704  		buildID string
   705  		tagID   string
   706  		tunnel  config.Tunnel
   707  	}
   708  	tests := []struct {
   709  		name    string
   710  		args    args
   711  		want    apitest.AsyncResponse
   712  		wantErr bool
   713  	}{
   714  		{
   715  			name: "Basic trigger",
   716  			args: args{
   717  				ctx:    context.Background(),
   718  				hookID: "dummyHookId",
   719  				tagID:  "dummyTag",
   720  			},
   721  			want: apitest.AsyncResponse{
   722  				ContextIDs: []string{"221270ac-0229-49d1-9025-251a10e9133d"},
   723  				EventIDs:   []string{"c4ca4238a0b923820dcc509a"},
   724  				TaskID:     "6ddf80b7-9753-4802-992b-d42948cdb99f",
   725  				TestIDs:    []string{"c20ad4d76fe97759aa27a0c9"},
   726  			},
   727  		},
   728  	}
   729  
   730  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   731  		var err error
   732  		if r.Method != http.MethodPost {
   733  			w.WriteHeader(http.StatusNotImplemented)
   734  			return
   735  		}
   736  		switch r.URL.Path {
   737  		case "/api-testing/rest/v4/dummyHookId/tests/_tag/dummyTag/_run":
   738  			completeStatusResp := []byte(`{"contextIds":["221270ac-0229-49d1-9025-251a10e9133d"],"eventIds":["c4ca4238a0b923820dcc509a"],"taskId":"6ddf80b7-9753-4802-992b-d42948cdb99f","testIds":["c20ad4d76fe97759aa27a0c9"]}`)
   739  			_, err = w.Write(completeStatusResp)
   740  		default:
   741  			w.WriteHeader(http.StatusInternalServerError)
   742  		}
   743  
   744  		if err != nil {
   745  			t.Errorf("failed to respond: %v", err)
   746  		}
   747  	}))
   748  	defer ts.Close()
   749  	c := &APITester{
   750  		HTTPClient: createTestRetryableHTTPClient(t),
   751  		URL:        ts.URL,
   752  		Username:   "dummyUser",
   753  		AccessKey:  "dummyAccesKey",
   754  	}
   755  
   756  	for _, tt := range tests {
   757  		t.Run(tt.name, func(t *testing.T) {
   758  			got, err := c.RunTagAsync(tt.args.ctx, tt.args.hookID, tt.args.tagID, tt.args.buildID, tt.args.tunnel, apitest.TestRequest{})
   759  			if (err != nil) != tt.wantErr {
   760  				t.Errorf("RunAllAsync() error = %v, wantErr %v", err, tt.wantErr)
   761  				return
   762  			}
   763  			if !cmp.Equal(got, tt.want) {
   764  				t.Errorf("RunAllAsync() got = %v, want %v", got, tt.want)
   765  			}
   766  		})
   767  	}
   768  }