github.com/akamai/AkamaiOPEN-edgegrid-golang/v8@v8.1.0/pkg/clientlists/client_list_test.go (about)

     1  package clientlists
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"io/ioutil"
     8  	"net/http"
     9  	"net/http/httptest"
    10  	"testing"
    11  
    12  	"github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session"
    13  	"github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/tools"
    14  	"github.com/stretchr/testify/require"
    15  	"github.com/tj/assert"
    16  )
    17  
    18  func TestGetClientLists(t *testing.T) {
    19  	uri := "/client-list/v1/lists"
    20  
    21  	tests := map[string]struct {
    22  		params           GetClientListsRequest
    23  		responseStatus   int
    24  		responseBody     string
    25  		expectedPath     string
    26  		expectedResponse *GetClientListsResponse
    27  		withError        error
    28  	}{
    29  		"200 OK": {
    30  			params:         GetClientListsRequest{},
    31  			responseStatus: http.StatusOK,
    32  			responseBody: `
    33  			{
    34  				"content": [
    35  					{
    36  						"createDate": "2023-06-06T15:58:39.225+00:00",
    37  						"createdBy": "ccare2",
    38  						"deprecated": false,
    39  						"filePrefix": "CL",
    40  						"itemsCount": 1,
    41  						"listId": "91596_AUDITLOGSTESTLIST",
    42  						"listType": "CL",
    43  						"name": "AUDIT LOGS - TEST LIST",
    44  						"productionActivationStatus": "INACTIVE",
    45  						"readOnly": false,
    46  						"shared": false,
    47  						"stagingActivationStatus": "INACTIVE",
    48  						"tags": ["green"],
    49  						"type": "IP",
    50  						"updateDate": "2023-06-06T15:58:39.225+00:00",
    51  						"updatedBy": "ccare2",
    52  						"version": 1
    53  					},
    54  					{
    55  						"createDate": "2022-11-10T14:42:04.857+00:00",
    56  						"createdBy": "ccare2",
    57  						"deprecated": false,
    58  						"filePrefix": "CL",
    59  						"itemsCount": 2,
    60  						"listId": "85988_ANTHONYGEOLISTOPEN",
    61  						"listType": "CL",
    62  						"name": "AnthonyGeoListOPEN",
    63  						"notes": "This is another Geo client list for Nov 11",
    64  						"productionActivationStatus": "INACTIVE",
    65  						"readOnly": false,
    66  						"shared": false,
    67  						"stagingActivationStatus": "INACTIVE",
    68  						"tags": [],
    69  						"type": "GEO",
    70  						"updateDate": "2023-05-11T15:30:10.224+00:00",
    71  						"updatedBy": "ccare2",
    72  						"version": 66
    73  					},
    74  					{
    75  						"createDate": "2022-10-17T13:39:25.319+00:00",
    76  						"createdBy": "ccare2",
    77  						"deprecated": false,
    78  						"filePrefix": "CL",
    79  						"itemsCount": 0,
    80  						"listId": "85552_ANTHONYFILEHASHLIST",
    81  						"listType": "CL",
    82  						"name": "File Hash List",
    83  						"notes": "This is another File hash client list for Oct 17",
    84  						"productionActivationStatus": "PENDING_ACTIVATION",
    85  						"readOnly": false,
    86  						"shared": false,
    87  						"stagingActivationStatus": "INACTIVE",
    88  						"tags": ["blue"],
    89  						"type": "TLS_FINGERPRINT",
    90  						"updateDate": "2023-06-05T06:56:19.004+00:00",
    91  						"updatedBy": "ccare2",
    92  						"version": 343
    93  					}
    94  				]
    95  			}
    96  			`,
    97  			expectedPath: uri,
    98  			expectedResponse: &GetClientListsResponse{
    99  				Content: []ClientList{
   100  					{
   101  						ListContent: ListContent{
   102  							CreateDate:                 "2023-06-06T15:58:39.225+00:00",
   103  							CreatedBy:                  "ccare2",
   104  							Deprecated:                 false,
   105  							ItemsCount:                 1,
   106  							ListID:                     "91596_AUDITLOGSTESTLIST",
   107  							ListType:                   "CL",
   108  							Name:                       "AUDIT LOGS - TEST LIST",
   109  							ProductionActivationStatus: "INACTIVE",
   110  							ReadOnly:                   false,
   111  							Shared:                     false,
   112  							StagingActivationStatus:    "INACTIVE",
   113  							Tags:                       []string{"green"},
   114  							Type:                       "IP",
   115  							UpdateDate:                 "2023-06-06T15:58:39.225+00:00",
   116  							UpdatedBy:                  "ccare2",
   117  							Version:                    1,
   118  						},
   119  					},
   120  					{
   121  						ListContent: ListContent{
   122  							CreateDate:                 "2022-11-10T14:42:04.857+00:00",
   123  							CreatedBy:                  "ccare2",
   124  							Deprecated:                 false,
   125  							ItemsCount:                 2,
   126  							ListID:                     "85988_ANTHONYGEOLISTOPEN",
   127  							ListType:                   "CL",
   128  							Name:                       "AnthonyGeoListOPEN",
   129  							Notes:                      "This is another Geo client list for Nov 11",
   130  							ProductionActivationStatus: "INACTIVE",
   131  							ReadOnly:                   false,
   132  							Shared:                     false,
   133  							StagingActivationStatus:    "INACTIVE",
   134  							Tags:                       []string{},
   135  							Type:                       "GEO",
   136  							UpdateDate:                 "2023-05-11T15:30:10.224+00:00",
   137  							UpdatedBy:                  "ccare2",
   138  							Version:                    66,
   139  						},
   140  					},
   141  					{
   142  						ListContent: ListContent{
   143  							CreateDate:                 "2022-10-17T13:39:25.319+00:00",
   144  							CreatedBy:                  "ccare2",
   145  							Deprecated:                 false,
   146  							ItemsCount:                 0,
   147  							ListID:                     "85552_ANTHONYFILEHASHLIST",
   148  							ListType:                   "CL",
   149  							Name:                       "File Hash List",
   150  							Notes:                      "This is another File hash client list for Oct 17",
   151  							ProductionActivationStatus: "PENDING_ACTIVATION",
   152  							ReadOnly:                   false,
   153  							Shared:                     false,
   154  							StagingActivationStatus:    "INACTIVE",
   155  							Tags:                       []string{"blue"},
   156  							Type:                       "TLS_FINGERPRINT",
   157  							UpdateDate:                 "2023-06-05T06:56:19.004+00:00",
   158  							UpdatedBy:                  "ccare2",
   159  							Version:                    343,
   160  						},
   161  					},
   162  				},
   163  			},
   164  		},
   165  		"200 OK - Lists filtered by name and type": {
   166  			params: GetClientListsRequest{
   167  				Name: "list name",
   168  				Type: []ClientListType{IP, GEO},
   169  			},
   170  			responseStatus: http.StatusOK,
   171  			responseBody: `
   172  			{
   173  				"content": [
   174  					{
   175  						"createDate": "2023-06-06T15:58:39.225+00:00",
   176  						"createdBy": "ccare2",
   177  						"deprecated": false,
   178  						"filePrefix": "CL",
   179  						"itemsCount": 1,
   180  						"listId": "91596_AUDITLOGSTESTLIST",
   181  						"listType": "CL",
   182  						"name": "AUDIT LOGS - TEST LIST",
   183  						"productionActivationStatus": "INACTIVE",
   184  						"readOnly": false,
   185  						"shared": false,
   186  						"stagingActivationStatus": "INACTIVE",
   187  						"tags": ["green"],
   188  						"type": "IP",
   189  						"updateDate": "2023-06-06T15:58:39.225+00:00",
   190  						"updatedBy": "ccare2",
   191  						"version": 1
   192  					}
   193  				]
   194  			}
   195  			`,
   196  			expectedPath: fmt.Sprintf(uri+"?name=%s&type=%s&type=%s", "list+name", "IP", "GEO"),
   197  			expectedResponse: &GetClientListsResponse{
   198  				Content: []ClientList{
   199  					{
   200  						ListContent: ListContent{
   201  							CreateDate:                 "2023-06-06T15:58:39.225+00:00",
   202  							CreatedBy:                  "ccare2",
   203  							Deprecated:                 false,
   204  							ItemsCount:                 1,
   205  							ListID:                     "91596_AUDITLOGSTESTLIST",
   206  							ListType:                   "CL",
   207  							Name:                       "AUDIT LOGS - TEST LIST",
   208  							ProductionActivationStatus: "INACTIVE",
   209  							ReadOnly:                   false,
   210  							Shared:                     false,
   211  							StagingActivationStatus:    "INACTIVE",
   212  							Tags:                       []string{"green"},
   213  							Type:                       "IP",
   214  							UpdateDate:                 "2023-06-06T15:58:39.225+00:00",
   215  							UpdatedBy:                  "ccare2",
   216  							Version:                    1,
   217  						},
   218  					},
   219  				},
   220  			},
   221  		},
   222  		"200 OK - Lists filtered by search and query params: includeItems, includeDeprecated, includeNetworkList, page, pageSize, sort": {
   223  			params: GetClientListsRequest{
   224  				Search:             "search term",
   225  				IncludeItems:       true,
   226  				IncludeDeprecated:  true,
   227  				IncludeNetworkList: true,
   228  				Page:               tools.IntPtr(0),
   229  				PageSize:           tools.IntPtr(2),
   230  				Sort:               []string{"updatedBy:desc", "value:desc"},
   231  			},
   232  			responseStatus: http.StatusOK,
   233  			responseBody: `
   234  			{
   235  				"content": [
   236  					{
   237  						"createDate": "2023-06-06T15:58:39.225+00:00",
   238  						"createdBy": "ccare2",
   239  						"deprecated": false,
   240  						"filePrefix": "CL",
   241  						"itemsCount": 1,
   242  						"listId": "91596_AUDITLOGSTESTLIST",
   243  						"listType": "CL",
   244  						"name": "AUDIT LOGS - TEST LIST",
   245  						"productionActivationStatus": "INACTIVE",
   246  						"readOnly": false,
   247  						"shared": false,
   248  						"stagingActivationStatus": "INACTIVE",
   249  						"tags": ["green"],
   250  						"type": "IP",
   251  						"updateDate": "2023-06-06T15:58:39.225+00:00",
   252  						"updatedBy": "ccare2",
   253  						"version": 1,
   254  						"items": []
   255  					}
   256  				]
   257  			}`,
   258  			expectedPath: fmt.Sprintf(
   259  				uri+"?includeDeprecated=%s&includeItems=%s&includeNetworkList=%s&page=%d&pageSize=%d&search=%s&sort=%s&sort=%s",
   260  				"true", "true", "true", 0, 2, "search+term", "updatedBy%3Adesc", "value%3Adesc",
   261  			),
   262  			expectedResponse: &GetClientListsResponse{
   263  				Content: []ClientList{
   264  					{
   265  						ListContent: ListContent{
   266  							CreateDate:                 "2023-06-06T15:58:39.225+00:00",
   267  							CreatedBy:                  "ccare2",
   268  							Deprecated:                 false,
   269  							ItemsCount:                 1,
   270  							ListID:                     "91596_AUDITLOGSTESTLIST",
   271  							ListType:                   "CL",
   272  							Name:                       "AUDIT LOGS - TEST LIST",
   273  							ProductionActivationStatus: "INACTIVE",
   274  							ReadOnly:                   false,
   275  							Shared:                     false,
   276  							StagingActivationStatus:    "INACTIVE",
   277  							Tags:                       []string{"green"},
   278  							Type:                       "IP",
   279  							UpdateDate:                 "2023-06-06T15:58:39.225+00:00",
   280  							UpdatedBy:                  "ccare2",
   281  							Version:                    1,
   282  						},
   283  						Items: []ListItemContent{},
   284  					},
   285  				},
   286  			},
   287  		},
   288  		"500 internal server error": {
   289  			params:         GetClientListsRequest{},
   290  			responseStatus: http.StatusInternalServerError,
   291  			responseBody: `
   292  				{
   293  					"type": "internal_error",
   294  					"title": "Internal Server Error",
   295  					"detail": "Error fetching client lists",
   296  					"status": 500
   297  				}`,
   298  			expectedPath: uri,
   299  			withError: &Error{
   300  				Type:       "internal_error",
   301  				Title:      "Internal Server Error",
   302  				Detail:     "Error fetching client lists",
   303  				StatusCode: http.StatusInternalServerError,
   304  			},
   305  		},
   306  	}
   307  
   308  	for name, test := range tests {
   309  		t.Run(name, func(t *testing.T) {
   310  			mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   311  				assert.Equal(t, test.expectedPath, r.URL.String())
   312  				assert.Equal(t, http.MethodGet, r.Method)
   313  				w.WriteHeader(test.responseStatus)
   314  				_, err := w.Write([]byte(test.responseBody))
   315  				assert.NoError(t, err)
   316  			}))
   317  			client := mockAPIClient(t, mockServer)
   318  			result, err := client.GetClientLists(
   319  				context.Background(),
   320  				test.params)
   321  			if test.withError != nil {
   322  				assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err)
   323  				return
   324  			}
   325  			require.NoError(t, err)
   326  			assert.Equal(t, test.expectedResponse, result)
   327  		})
   328  	}
   329  }
   330  
   331  func TestGetClientList(t *testing.T) {
   332  	uri := "/client-list/v1/lists/12_AB?includeItems=true"
   333  
   334  	tests := map[string]struct {
   335  		params           GetClientListRequest
   336  		responseStatus   int
   337  		responseBody     string
   338  		expectedPath     string
   339  		expectedResponse *GetClientListResponse
   340  		withError        error
   341  	}{
   342  		"200 OK": {
   343  			params: GetClientListRequest{
   344  				ListID:       "12_AB",
   345  				IncludeItems: true,
   346  			},
   347  			responseStatus: http.StatusOK,
   348  			responseBody: `{
   349  				"createDate": "2023-06-06T15:58:39.225+00:00",
   350  				"createdBy": "ccare2",
   351  				"deprecated": false,
   352  				"filePrefix": "CL",
   353  				"itemsCount": 1,
   354  				"listId": "12_AB",
   355  				"listType": "CL",
   356  				"name": "AUDIT LOGS - TEST LIST",
   357  				"productionActivationStatus": "INACTIVE",
   358  				"readOnly": false,
   359  				"shared": false,
   360  				"stagingActivationStatus": "INACTIVE",
   361  				"productionActiveVersion": 2,
   362  				"stagingActiveVersion": 2,
   363  				"tags": ["green"],
   364  				"type": "IP",
   365  				"updateDate": "2023-06-06T15:58:39.225+00:00",
   366  				"updatedBy": "ccare2",
   367  				"version": 1,
   368  				"groupId": 12,
   369  				"groupName": "123_ABC",
   370  				"contractId" :"12_CO",
   371  				"items": [
   372  					{
   373  						"createDate": "2022-07-12T20:14:29.189+00:00",
   374  						"createdBy": "ccare2",
   375  						"createdVersion": 9,
   376  						"productionStatus": "INACTIVE",
   377  						"stagingStatus": "PENDING_ACTIVATION",
   378  						"tags": [],
   379  						"type": "IP",
   380  						"updateDate": "2022-07-12T20:14:29.189+00:00",
   381  						"updatedBy": "ccare2",
   382  						"value": "7d0:1:0::0/64"
   383  					},
   384  					{
   385              "createDate": "2022-07-12T20:14:29.189+00:00",
   386              "createdBy": "ccare2",
   387              "createdVersion": 9,
   388              "description": "Item with description, tags, expiration date",
   389              "expirationDate": "2030-12-31T12:40:00.000+00:00",
   390              "productionStatus": "INACTIVE",
   391              "stagingStatus": "PENDING_ACTIVATION",
   392              "tags": [
   393                  "red",
   394                  "green",
   395                  "blue"
   396              ],
   397              "type": "IP",
   398              "updateDate": "2022-07-12T20:14:29.189+00:00",
   399              "updatedBy": "ccare2",
   400              "value": "7d0:1:1::0/64"
   401          	}
   402  				]
   403  			}`,
   404  			expectedPath: uri,
   405  			expectedResponse: &GetClientListResponse{
   406  				ListContent: ListContent{
   407  					CreateDate:                 "2023-06-06T15:58:39.225+00:00",
   408  					CreatedBy:                  "ccare2",
   409  					Deprecated:                 false,
   410  					ItemsCount:                 1,
   411  					ListID:                     "12_AB",
   412  					ListType:                   "CL",
   413  					Name:                       "AUDIT LOGS - TEST LIST",
   414  					ProductionActivationStatus: "INACTIVE",
   415  					ReadOnly:                   false,
   416  					Shared:                     false,
   417  					StagingActivationStatus:    "INACTIVE",
   418  					ProductionActiveVersion:    2,
   419  					StagingActiveVersion:       2,
   420  					Tags:                       []string{"green"},
   421  					Type:                       "IP",
   422  					UpdateDate:                 "2023-06-06T15:58:39.225+00:00",
   423  					UpdatedBy:                  "ccare2",
   424  					Version:                    1,
   425  				},
   426  				GroupID:    12,
   427  				GroupName:  "123_ABC",
   428  				ContractID: "12_CO",
   429  				Items: []ListItemContent{
   430  					{
   431  						CreateDate:       "2022-07-12T20:14:29.189+00:00",
   432  						CreatedBy:        "ccare2",
   433  						CreatedVersion:   9,
   434  						ProductionStatus: "INACTIVE",
   435  						StagingStatus:    "PENDING_ACTIVATION",
   436  						Tags:             []string{},
   437  						Type:             "IP",
   438  						UpdateDate:       "2022-07-12T20:14:29.189+00:00",
   439  						UpdatedBy:        "ccare2",
   440  						Value:            "7d0:1:0::0/64",
   441  					},
   442  					{
   443  						CreateDate:       "2022-07-12T20:14:29.189+00:00",
   444  						CreatedBy:        "ccare2",
   445  						CreatedVersion:   9,
   446  						ProductionStatus: "INACTIVE",
   447  						StagingStatus:    "PENDING_ACTIVATION",
   448  						Tags:             []string{"red", "green", "blue"},
   449  						Description:      "Item with description, tags, expiration date",
   450  						ExpirationDate:   "2030-12-31T12:40:00.000+00:00",
   451  						Type:             "IP",
   452  						UpdateDate:       "2022-07-12T20:14:29.189+00:00",
   453  						UpdatedBy:        "ccare2",
   454  						Value:            "7d0:1:1::0/64",
   455  					},
   456  				},
   457  			},
   458  		},
   459  		"500 internal server error": {
   460  			params: GetClientListRequest{
   461  				ListID:       "12_AB",
   462  				IncludeItems: true,
   463  			},
   464  			responseStatus: http.StatusInternalServerError,
   465  			responseBody: `
   466  				{
   467  					"type": "internal_error",
   468  					"title": "Internal Server Error",
   469  					"detail": "Error fetching client lists",
   470  					"status": 500
   471  				}`,
   472  			expectedPath: uri,
   473  			withError: &Error{
   474  				Type:       "internal_error",
   475  				Title:      "Internal Server Error",
   476  				Detail:     "Error fetching client lists",
   477  				StatusCode: http.StatusInternalServerError,
   478  			},
   479  		},
   480  		"validation error": {
   481  			params:    GetClientListRequest{},
   482  			withError: ErrStructValidation,
   483  		},
   484  	}
   485  
   486  	for name, test := range tests {
   487  		t.Run(name, func(t *testing.T) {
   488  			mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   489  				assert.Equal(t, test.expectedPath, r.URL.String())
   490  				assert.Equal(t, http.MethodGet, r.Method)
   491  				w.WriteHeader(test.responseStatus)
   492  				_, err := w.Write([]byte(test.responseBody))
   493  				assert.NoError(t, err)
   494  			}))
   495  			client := mockAPIClient(t, mockServer)
   496  			result, err := client.GetClientList(
   497  				session.ContextWithOptions(
   498  					context.Background(),
   499  				),
   500  				test.params)
   501  			if test.withError != nil {
   502  				assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err)
   503  				return
   504  			}
   505  			require.NoError(t, err)
   506  			assert.Equal(t, test.expectedResponse, result)
   507  		})
   508  	}
   509  }
   510  
   511  func TestUpdateClientList(t *testing.T) {
   512  	uri := "/client-list/v1/lists/12_12"
   513  	request := UpdateClientListRequest{
   514  		UpdateClientList: UpdateClientList{
   515  			Name:  "Some New Name",
   516  			Tags:  []string{"red"},
   517  			Notes: "Updating list notes",
   518  		},
   519  		ListID: "12_12",
   520  	}
   521  	result := UpdateClientListResponse{
   522  		ContractID: "M-2CF0QRI",
   523  		GroupName:  "Kona QA16-M-2CF0QRI",
   524  		GroupID:    12,
   525  		ListContent: ListContent{
   526  			CreateDate:                 "2023-04-03T15:50:34.074+00:00",
   527  			CreatedBy:                  "ccare2",
   528  			Deprecated:                 false,
   529  			ItemsCount:                 51,
   530  			ListID:                     "12_12",
   531  			ListType:                   "CL",
   532  			Name:                       "Some New Name",
   533  			Tags:                       []string{"red"},
   534  			Notes:                      "Updating list notes",
   535  			ProductionActivationStatus: "INACTIVE",
   536  			ReadOnly:                   false,
   537  			Shared:                     false,
   538  			StagingActivationStatus:    "INACTIVE",
   539  			ProductionActiveVersion:    2,
   540  			StagingActiveVersion:       2,
   541  			Type:                       "IP",
   542  			UpdateDate:                 "2023-06-15T20:28:09.047+00:00",
   543  			UpdatedBy:                  "ccare2",
   544  			Version:                    75,
   545  		},
   546  	}
   547  
   548  	tests := map[string]struct {
   549  		params              UpdateClientListRequest
   550  		expectedRequestBody string
   551  		responseStatus      int
   552  		responseBody        string
   553  		expectedPath        string
   554  		expectedResponse    *UpdateClientListResponse
   555  		withError           error
   556  	}{
   557  		"200 OK": {
   558  			params:              request,
   559  			expectedRequestBody: `{"name":"Some New Name","notes":"Updating list notes","tags":["red"]}`,
   560  			responseStatus:      http.StatusOK,
   561  			responseBody: `{
   562  				"contractId": "M-2CF0QRI",
   563  				"createDate": "2023-04-03T15:50:34.074+00:00",
   564  				"createdBy": "ccare2",
   565  				"deprecated": false,
   566  				"filePrefix": "CL",
   567  				"groupName": "Kona QA16-M-2CF0QRI",
   568  				"groupId": 12,
   569  				"itemsCount": 51,
   570  				"listId": "12_12",
   571  				"listType": "CL",
   572  				"name": "Some New Name",
   573  				"tags": [ "red"],
   574  				"notes": "Updating list notes",
   575  				"productionActivationStatus": "INACTIVE",
   576  				"readOnly": false,
   577  				"shared": false,
   578  				"stagingActivationStatus": "INACTIVE",
   579  				"productionActiveVersion":    2,
   580  				"stagingActiveVersion":       2,
   581  				"type": "IP",
   582  				"updateDate": "2023-06-15T20:28:09.047+00:00",
   583  				"updatedBy": "ccare2",
   584  				"version": 75
   585  			}`,
   586  			expectedPath:     uri,
   587  			expectedResponse: &result,
   588  		},
   589  		"500 internal server error": {
   590  			params:         request,
   591  			responseStatus: http.StatusInternalServerError,
   592  			responseBody: `
   593  				{
   594  					"type": "internal_error",
   595  					"title": "Internal Server Error",
   596  					"detail": "Error fetching client lists",
   597  					"status": 500
   598  				}`,
   599  			expectedPath: uri,
   600  			withError: &Error{
   601  				Type:       "internal_error",
   602  				Title:      "Internal Server Error",
   603  				Detail:     "Error fetching client lists",
   604  				StatusCode: http.StatusInternalServerError,
   605  			},
   606  		},
   607  		"validation error": {
   608  			params:    UpdateClientListRequest{},
   609  			withError: ErrStructValidation,
   610  		},
   611  	}
   612  
   613  	for name, test := range tests {
   614  		t.Run(name, func(t *testing.T) {
   615  			mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   616  				assert.Equal(t, test.expectedPath, r.URL.String())
   617  				assert.Equal(t, http.MethodPut, r.Method)
   618  				w.WriteHeader(test.responseStatus)
   619  				_, err := w.Write([]byte(test.responseBody))
   620  				assert.NoError(t, err)
   621  
   622  				if len(test.expectedRequestBody) > 0 {
   623  					body, err := ioutil.ReadAll(r.Body)
   624  					require.NoError(t, err)
   625  					assert.Equal(t, test.expectedRequestBody, string(body))
   626  				}
   627  			}))
   628  			client := mockAPIClient(t, mockServer)
   629  			result, err := client.UpdateClientList(
   630  				context.Background(),
   631  				test.params)
   632  			if test.withError != nil {
   633  				assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err)
   634  				return
   635  			}
   636  			require.NoError(t, err)
   637  			assert.Equal(t, test.expectedResponse, result)
   638  		})
   639  	}
   640  }
   641  func TestUpdateClientListItems(t *testing.T) {
   642  	uri := "/client-list/v1/lists/12_12/items"
   643  	request := UpdateClientListItemsRequest{
   644  		ListID: "12_12",
   645  		UpdateClientListItems: UpdateClientListItems{
   646  			Append: []ListItemPayload{
   647  				{
   648  					Description:    "Lorem Ipsum has been the industry's standard dummy text ever since the 1500s...",
   649  					ExpirationDate: "2026-12-26T01:32:08.375+00:00",
   650  					Value:          "1.1.1.72",
   651  				},
   652  			},
   653  			Update: []ListItemPayload{
   654  				{
   655  					Description:    "remove exp date and tags",
   656  					ExpirationDate: "",
   657  					Tags:           []string{"t"},
   658  					Value:          "1.1.1.45",
   659  				},
   660  				{
   661  					ExpirationDate: "2028-11-26T17:32:08.375+00:00",
   662  					Value:          "1.1.1.33",
   663  				},
   664  			},
   665  			Delete: []ListItemPayload{
   666  				{
   667  					Value: "1.1.1.38",
   668  				},
   669  			},
   670  		},
   671  	}
   672  	result := UpdateClientListItemsResponse{
   673  		Appended: []ListItemContent{
   674  			{
   675  				Description:      "Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley",
   676  				ExpirationDate:   "2026-12-26T01:32:08.375+00:00",
   677  				Tags:             []string{"new tag"},
   678  				Value:            "1.1.1.75",
   679  				CreateDate:       "2023-06-15T20:46:30.780+00:00",
   680  				CreatedBy:        "ccare2",
   681  				CreatedVersion:   76,
   682  				ProductionStatus: "INACTIVE",
   683  				StagingStatus:    "INACTIVE",
   684  				Type:             "IP",
   685  				UpdateDate:       "2023-06-15T20:46:30.780+00:00",
   686  				UpdatedBy:        "ccare2",
   687  			},
   688  		},
   689  		Deleted: []ListItemContent{
   690  			{
   691  				Value: "1.1.1.39",
   692  			},
   693  		},
   694  		Updated: []ListItemContent{
   695  			{
   696  				Description:      "remove exp date and tags",
   697  				Tags:             []string{"t1"},
   698  				Value:            "1.1.1.45",
   699  				CreateDate:       "2023-04-28T19:34:00.906+00:00",
   700  				CreatedBy:        "ccare2",
   701  				CreatedVersion:   54,
   702  				ProductionStatus: "INACTIVE",
   703  				StagingStatus:    "INACTIVE",
   704  				Type:             "IP",
   705  				UpdateDate:       "2023-06-15T20:46:30.765+00:00",
   706  				UpdatedBy:        "ccare2",
   707  			},
   708  		},
   709  	}
   710  
   711  	tests := map[string]struct {
   712  		params              UpdateClientListItemsRequest
   713  		expectedRequestBody string
   714  		responseStatus      int
   715  		responseBody        string
   716  		expectedPath        string
   717  		expectedResponse    *UpdateClientListItemsResponse
   718  		withError           error
   719  	}{
   720  		"200 OK": {
   721  			params:              request,
   722  			expectedRequestBody: `{"append":[{"value":"1.1.1.72","tags":null,"description":"Lorem Ipsum has been the industry's standard dummy text ever since the 1500s...","expirationDate":"2026-12-26T01:32:08.375+00:00"}],"update":[{"value":"1.1.1.45","tags":["t"],"description":"remove exp date and tags","expirationDate":""},{"value":"1.1.1.33","tags":null,"description":"","expirationDate":"2028-11-26T17:32:08.375+00:00"}],"delete":[{"value":"1.1.1.38","tags":null,"description":"","expirationDate":""}]}`,
   723  			responseStatus:      http.StatusOK,
   724  			responseBody: `{
   725  				"appended": [
   726  					{
   727  						"createDate": "2023-06-15T20:46:30.780+00:00",
   728  						"createdBy": "ccare2",
   729  						"createdVersion": 76,
   730  						"description": "Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley",
   731  						"expirationDate": "2026-12-26T01:32:08.375+00:00",
   732  						"productionStatus": "INACTIVE",
   733  						"stagingStatus": "INACTIVE",
   734  						"tags": [
   735  							"new tag"
   736  						],
   737  						"type": "IP",
   738  						"updateDate": "2023-06-15T20:46:30.780+00:00",
   739  						"updatedBy": "ccare2",
   740  						"value": "1.1.1.75"
   741  					}
   742  				],
   743  				"deleted": [
   744  					{
   745  						"value": "1.1.1.39"
   746  					}
   747  				],
   748  				"updated": [
   749  					{
   750  						"createDate": "2023-04-28T19:34:00.906+00:00",
   751  						"createdBy": "ccare2",
   752  						"createdVersion": 54,
   753  						"description": "remove exp date and tags",
   754  						"productionStatus": "INACTIVE",
   755  						"stagingStatus": "INACTIVE",
   756  						"tags": [
   757  							"t1"
   758  						],
   759  						"type": "IP",
   760  						"updateDate": "2023-06-15T20:46:30.765+00:00",
   761  						"updatedBy": "ccare2",
   762  						"value": "1.1.1.45"
   763  					}
   764  				]
   765  			}`,
   766  			expectedPath:     uri,
   767  			expectedResponse: &result,
   768  		},
   769  		"500 internal server error": {
   770  			params:         request,
   771  			responseStatus: http.StatusInternalServerError,
   772  			responseBody: `
   773  				{
   774  					"type": "internal_error",
   775  					"title": "Internal Server Error",
   776  					"detail": "Error fetching client lists",
   777  					"status": 500
   778  				}`,
   779  			expectedPath: uri,
   780  			withError: &Error{
   781  				Type:       "internal_error",
   782  				Title:      "Internal Server Error",
   783  				Detail:     "Error fetching client lists",
   784  				StatusCode: http.StatusInternalServerError,
   785  			},
   786  		},
   787  		"validation error": {
   788  			params:    UpdateClientListItemsRequest{},
   789  			withError: ErrStructValidation,
   790  		},
   791  	}
   792  
   793  	for name, test := range tests {
   794  		t.Run(name, func(t *testing.T) {
   795  			mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   796  				assert.Equal(t, test.expectedPath, r.URL.String())
   797  				assert.Equal(t, http.MethodPost, r.Method)
   798  				w.WriteHeader(test.responseStatus)
   799  				_, err := w.Write([]byte(test.responseBody))
   800  				assert.NoError(t, err)
   801  
   802  				if len(test.expectedRequestBody) > 0 {
   803  					body, err := ioutil.ReadAll(r.Body)
   804  					require.NoError(t, err)
   805  					assert.Equal(t, test.expectedRequestBody, string(body))
   806  				}
   807  			}))
   808  			client := mockAPIClient(t, mockServer)
   809  			result, err := client.UpdateClientListItems(
   810  				context.Background(),
   811  				test.params)
   812  			if test.withError != nil {
   813  				assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err)
   814  				return
   815  			}
   816  			require.NoError(t, err)
   817  			assert.Equal(t, test.expectedResponse, result)
   818  		})
   819  	}
   820  }
   821  
   822  func TestCreateClientLists(t *testing.T) {
   823  	uri := "/client-list/v1/lists"
   824  	request := CreateClientListRequest{
   825  		Name:       "TEST LIST",
   826  		Type:       "IP",
   827  		Notes:      "Some notes",
   828  		Tags:       []string{"red", "green"},
   829  		ContractID: "M-2CF0QRI",
   830  		GroupID:    112524,
   831  		Items: []ListItemPayload{
   832  			{
   833  				Value:          "1.1.1.1",
   834  				Description:    "some description",
   835  				Tags:           []string{},
   836  				ExpirationDate: "2026-12-26T01:32:08.375+00:00",
   837  			},
   838  		},
   839  	}
   840  	result := CreateClientListResponse{
   841  		ListContent: ListContent{
   842  			ListID: "123_ABC",
   843  			Name:   "TEST LIST",
   844  			Type:   "IP",
   845  			Notes:  "Some notes",
   846  			Tags:   []string{"red", "green"},
   847  		},
   848  		ContractID: "M-2CF0QRI",
   849  		GroupName:  "Group A",
   850  		GroupID:    12,
   851  		Items: []ListItemContent{
   852  			{
   853  				Value:          "1.1.1.1",
   854  				Description:    "",
   855  				Tags:           []string{},
   856  				ExpirationDate: "2026-12-26T01:32:08.375+00:00",
   857  			},
   858  		},
   859  	}
   860  
   861  	tests := map[string]struct {
   862  		params              CreateClientListRequest
   863  		expectedRequestBody string
   864  		responseStatus      int
   865  		responseBody        string
   866  		expectedPath        string
   867  		expectedResponse    *CreateClientListResponse
   868  		withError           error
   869  	}{
   870  		"201 Created": {
   871  			params:              request,
   872  			expectedRequestBody: `{"contractId":"M-2CF0QRI","groupId":112524,"name":"TEST LIST","type":"IP","notes":"Some notes","tags":["red","green"],"items":[{"value":"1.1.1.1","tags":[],"description":"some description","expirationDate":"2026-12-26T01:32:08.375+00:00"}]}`,
   873  			responseStatus:      http.StatusCreated,
   874  			responseBody: `{
   875  				"listId": "123_ABC",
   876  				"name": "TEST LIST",
   877  				"type": "IP",
   878  				"notes": "Some notes",
   879  				"tags": [
   880  					"red",
   881  					"green"
   882  				],
   883  				"contractId": "M-2CF0QRI",
   884  				"groupName": "Group A",
   885  				"groupId": 12,
   886  				"items": [
   887  					{
   888  						"value": "1.1.1.1",
   889  						"description": "",
   890  						"tags": [],
   891  						"expirationDate": "2026-12-26T01:32:08.375+00:00"
   892  					}
   893  				]
   894  			}
   895  			`,
   896  			expectedPath:     uri,
   897  			expectedResponse: &result,
   898  		},
   899  		"500 internal server error": {
   900  			params:         request,
   901  			responseStatus: http.StatusInternalServerError,
   902  			responseBody: `
   903  				{
   904  					"type": "internal_error",
   905  					"title": "Internal Server Error",
   906  					"detail": "Error fetching client lists",
   907  					"status": 500
   908  				}`,
   909  			expectedPath: uri,
   910  			withError: &Error{
   911  				Type:       "internal_error",
   912  				Title:      "Internal Server Error",
   913  				Detail:     "Error fetching client lists",
   914  				StatusCode: http.StatusInternalServerError,
   915  			},
   916  		},
   917  		"validation error": {
   918  			params:    CreateClientListRequest{},
   919  			withError: ErrStructValidation,
   920  		},
   921  	}
   922  
   923  	for name, test := range tests {
   924  		t.Run(name, func(t *testing.T) {
   925  			mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   926  				assert.Equal(t, test.expectedPath, r.URL.String())
   927  				assert.Equal(t, http.MethodPost, r.Method)
   928  				w.WriteHeader(test.responseStatus)
   929  				_, err := w.Write([]byte(test.responseBody))
   930  				assert.NoError(t, err)
   931  
   932  				if len(test.expectedRequestBody) > 0 {
   933  					body, err := ioutil.ReadAll(r.Body)
   934  					require.NoError(t, err)
   935  					assert.Equal(t, test.expectedRequestBody, string(body))
   936  				}
   937  			}))
   938  			client := mockAPIClient(t, mockServer)
   939  			result, err := client.CreateClientList(
   940  				context.Background(),
   941  				test.params)
   942  			if test.withError != nil {
   943  				assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err)
   944  				return
   945  			}
   946  			require.NoError(t, err)
   947  			assert.Equal(t, test.expectedResponse, result)
   948  		})
   949  	}
   950  }
   951  
   952  func TestDeleteClientLists(t *testing.T) {
   953  	uri := "/client-list/v1/lists/12_AB"
   954  	request := DeleteClientListRequest{
   955  		ListID: "12_AB",
   956  	}
   957  
   958  	tests := map[string]struct {
   959  		params           DeleteClientListRequest
   960  		responseStatus   int
   961  		responseBody     string
   962  		expectedPath     string
   963  		expectedResponse *Error
   964  		withError        error
   965  	}{
   966  		"204 NoContent": {
   967  			params:           request,
   968  			responseBody:     "",
   969  			responseStatus:   http.StatusNoContent,
   970  			expectedPath:     uri,
   971  			expectedResponse: nil,
   972  		},
   973  		"500 internal server error": {
   974  			params:         request,
   975  			responseStatus: http.StatusInternalServerError,
   976  			responseBody: `
   977  				{
   978  					"type": "internal_error",
   979  					"title": "Internal Server Error",
   980  					"detail": "Error fetching client lists",
   981  					"status": 500
   982  				}`,
   983  			expectedPath: uri,
   984  			withError: &Error{
   985  				Type:       "internal_error",
   986  				Title:      "Internal Server Error",
   987  				Detail:     "Error fetching client lists",
   988  				StatusCode: http.StatusInternalServerError,
   989  			},
   990  		},
   991  		"validation error": {
   992  			params:    DeleteClientListRequest{},
   993  			withError: ErrStructValidation,
   994  		},
   995  	}
   996  
   997  	for name, test := range tests {
   998  		t.Run(name, func(t *testing.T) {
   999  			mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  1000  				assert.Equal(t, test.expectedPath, r.URL.String())
  1001  				assert.Equal(t, http.MethodDelete, r.Method)
  1002  				w.WriteHeader(test.responseStatus)
  1003  				_, err := w.Write([]byte(test.responseBody))
  1004  				assert.NoError(t, err)
  1005  			}))
  1006  			client := mockAPIClient(t, mockServer)
  1007  			err := client.DeleteClientList(
  1008  				context.Background(),
  1009  				test.params)
  1010  			if test.withError != nil {
  1011  				assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err)
  1012  				return
  1013  			}
  1014  			require.NoError(t, err)
  1015  		})
  1016  	}
  1017  }