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

     1  package papi
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"errors"
     7  	"fmt"
     8  	"net/http"
     9  	"net/http/httptest"
    10  	"testing"
    11  
    12  	"github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/tools"
    13  	"github.com/stretchr/testify/assert"
    14  	"github.com/stretchr/testify/require"
    15  )
    16  
    17  func TestPapi_GetRuleTree(t *testing.T) {
    18  	tests := map[string]struct {
    19  		params           GetRuleTreeRequest
    20  		responseStatus   int
    21  		responseBody     string
    22  		expectedPath     string
    23  		expectedResponse *GetRuleTreeResponse
    24  		withError        func(*testing.T, error)
    25  	}{
    26  		"200 OK": {
    27  			params: GetRuleTreeRequest{
    28  				PropertyID:      "propertyID",
    29  				PropertyVersion: 2,
    30  				ContractID:      "contract",
    31  				GroupID:         "group",
    32  				ValidateMode:    "fast",
    33  				ValidateRules:   false,
    34  			},
    35  			responseStatus: http.StatusOK,
    36  			responseBody: `
    37  {
    38      "accountId": "accountID",
    39      "contractId": "contract",
    40      "groupId": "group",
    41      "propertyId": "propertyID",
    42      "propertyVersion": 2,
    43      "etag": "etag",
    44      "ruleFormat": "v2020-09-16",
    45      "rules": {
    46          "name": "default",
    47          "criteria": [],
    48          "children": [
    49              {
    50                  "name": "Compress Text Content",
    51                  "criteria": [
    52                      {
    53                          "name": "contentType",
    54                          "options": {
    55                              "matchOperator": "IS_ONE_OF",
    56                              "matchWildcard": true,
    57                              "matchCaseSensitive": false,
    58                              "values": [
    59                                  "text/html*",
    60                                  "text/css*",
    61                                  "application/x-javascript*"
    62                              ]
    63                          }
    64                      }
    65                  ],
    66                  "behaviors": [
    67                      {
    68                          "name": "gzipResponse",
    69                          "options": { "behavior": "ALWAYS" }
    70                      }
    71                  ]
    72              }
    73          ],
    74          "options": {
    75              "is_secure": false
    76          },
    77          "behaviors": [
    78              {
    79                  "name": "origin",
    80                  "options": {
    81                      "httpPort": 80,
    82                      "enableTrueClientIp": false,
    83                      "compress": true,
    84                      "cacheKeyHostname": "ORIGIN_HOSTNAME",
    85                      "forwardHostHeader": "REQUEST_HOST_HEADER",
    86                      "hostname": "origin.test.com",
    87                      "originType": "CUSTOMER"
    88                  }
    89              },
    90              {
    91                  "name": "cpCode",
    92                  "options": {
    93                      "value": {
    94                          "id": 12345,
    95                          "name": "my CP code"
    96                      }
    97                  }
    98              }
    99          ],
   100   		"customOverride": {
   101          	"overrideId": "cbo_12345",
   102          	"name": "mdc"
   103      	},
   104  		"variables": [
   105              {
   106                  "name": "VAR_NAME",
   107                  "value": "default value",
   108                  "description": "This is a sample Property Manager variable.",
   109                  "hidden": false,
   110                  "sensitive": false
   111              }
   112          ]
   113      }
   114  }`,
   115  			expectedPath: "/papi/v1/properties/propertyID/versions/2/rules?contractId=contract&groupId=group&validateMode=fast&validateRules=false",
   116  			expectedResponse: &GetRuleTreeResponse{
   117  				Response: Response{
   118  					AccountID:  "accountID",
   119  					ContractID: "contract",
   120  					GroupID:    "group",
   121  				},
   122  				PropertyID:      "propertyID",
   123  				PropertyVersion: 2,
   124  				Etag:            "etag",
   125  				RuleFormat:      "v2020-09-16",
   126  				Rules: Rules{
   127  					Behaviors: []RuleBehavior{
   128  						{
   129  							Name: "origin",
   130  							Options: RuleOptionsMap{
   131  								"httpPort":           float64(80),
   132  								"enableTrueClientIp": false,
   133  								"compress":           true,
   134  								"cacheKeyHostname":   "ORIGIN_HOSTNAME",
   135  								"forwardHostHeader":  "REQUEST_HOST_HEADER",
   136  								"hostname":           "origin.test.com",
   137  								"originType":         "CUSTOMER",
   138  							},
   139  						},
   140  						{
   141  							Name: "cpCode",
   142  							Options: RuleOptionsMap{
   143  								"value": map[string]interface{}{
   144  									"id":   float64(12345),
   145  									"name": "my CP code",
   146  								},
   147  							},
   148  						},
   149  					},
   150  					Children: []Rules{
   151  						{
   152  							Behaviors: []RuleBehavior{
   153  								{
   154  									Name: "gzipResponse",
   155  									Options: RuleOptionsMap{
   156  										"behavior": "ALWAYS",
   157  									},
   158  								},
   159  							},
   160  							Criteria: []RuleBehavior{
   161  								{
   162  									Locked: false,
   163  									Name:   "contentType",
   164  									Options: RuleOptionsMap{
   165  										"matchOperator":      "IS_ONE_OF",
   166  										"matchWildcard":      true,
   167  										"matchCaseSensitive": false,
   168  										"values":             []interface{}{"text/html*", "text/css*", "application/x-javascript*"},
   169  									},
   170  								},
   171  							},
   172  							Name: "Compress Text Content",
   173  						},
   174  					},
   175  					Criteria: []RuleBehavior{},
   176  					Name:     "default",
   177  					Options:  RuleOptions{IsSecure: false},
   178  					CustomOverride: &RuleCustomOverride{
   179  						OverrideID: "cbo_12345",
   180  						Name:       "mdc",
   181  					},
   182  					Variables: []RuleVariable{
   183  						{
   184  							Description: tools.StringPtr("This is a sample Property Manager variable."),
   185  							Hidden:      false,
   186  							Name:        "VAR_NAME",
   187  							Sensitive:   false,
   188  							Value:       tools.StringPtr("default value"),
   189  						},
   190  					},
   191  				},
   192  			},
   193  		},
   194  		"200 OK nested with empty behaviour": {
   195  			params: GetRuleTreeRequest{
   196  				PropertyID:      "propertyID",
   197  				PropertyVersion: 2,
   198  				ContractID:      "contract",
   199  				GroupID:         "group",
   200  				ValidateMode:    "fast",
   201  				ValidateRules:   false,
   202  			},
   203  			responseStatus: http.StatusOK,
   204  			responseBody: `
   205  {
   206  	"accountId": "accountID",
   207  	"contractId": "contract",
   208  	"groupId": "group",
   209  	"propertyId": "propertyID",
   210  	"propertyVersion": 2,
   211  	"etag": "etag",
   212  	"ruleFormat": "v2020-09-16",
   213  	"rules": {
   214  		"name": "default",
   215  		"children": [{
   216  			"name": "Augment insights",
   217  			"criteria": [],
   218  			"children": [{
   219  				"name": "Compress Text Content",
   220  				"criteria": [{
   221  					"name": "contentType",
   222  					"options": {
   223  						"matchOperator": "IS_ONE_OF",
   224  						"matchWildcard": true,
   225  						"matchCaseSensitive": false,
   226  						"values": [
   227  							"text/html*",
   228  							"text/css*",
   229  							"application/x-javascript*"
   230  						]
   231  					}
   232  				}],
   233  				"behaviors": []
   234  			}],
   235  			"options": {
   236  				"is_secure": false
   237  			}
   238  		}],
   239  		"behaviors": [{
   240  				"name": "origin",
   241  				"options": {
   242  					"httpPort": 80,
   243  					"enableTrueClientIp": false,
   244  					"compress": true,
   245  					"cacheKeyHostname": "ORIGIN_HOSTNAME",
   246  					"forwardHostHeader": "REQUEST_HOST_HEADER",
   247  					"hostname": "origin.test.com",
   248  					"originType": "CUSTOMER"
   249  				}
   250  			},
   251  			{
   252  				"name": "cpCode",
   253  				"options": {
   254  					"value": {
   255  						"id": 12345,
   256  						"name": "my CP code"
   257  					}
   258  				}
   259  			}
   260  		],
   261  		"customOverride": {
   262  			"overrideId": "cbo_12345",
   263  			"name": "mdc"
   264  		},
   265  		"variables": [{
   266  			"name": "VAR_NAME",
   267  			"value": "default value",
   268  			"description": "This is a sample Property Manager variable.",
   269  			"hidden": false,
   270  			"sensitive": false
   271  		}]
   272  	}
   273  }`,
   274  			expectedPath: "/papi/v1/properties/propertyID/versions/2/rules?contractId=contract&groupId=group&validateMode=fast&validateRules=false",
   275  			expectedResponse: &GetRuleTreeResponse{
   276  				Response: Response{
   277  					AccountID:  "accountID",
   278  					ContractID: "contract",
   279  					GroupID:    "group",
   280  				},
   281  				PropertyID:      "propertyID",
   282  				PropertyVersion: 2,
   283  				Etag:            "etag",
   284  				RuleFormat:      "v2020-09-16",
   285  				Rules: Rules{
   286  					Behaviors: []RuleBehavior{
   287  						{
   288  							Name: "origin",
   289  							Options: RuleOptionsMap{
   290  								"httpPort":           float64(80),
   291  								"enableTrueClientIp": false,
   292  								"compress":           true,
   293  								"cacheKeyHostname":   "ORIGIN_HOSTNAME",
   294  								"forwardHostHeader":  "REQUEST_HOST_HEADER",
   295  								"hostname":           "origin.test.com",
   296  								"originType":         "CUSTOMER",
   297  							},
   298  						},
   299  						{
   300  							Name: "cpCode",
   301  							Options: RuleOptionsMap{
   302  								"value": map[string]interface{}{
   303  									"id":   float64(12345),
   304  									"name": "my CP code",
   305  								},
   306  							},
   307  						},
   308  					},
   309  					Children: []Rules{
   310  						{
   311  							Criteria: []RuleBehavior{},
   312  							Name:     "Augment insights",
   313  
   314  							Children: []Rules{
   315  								{
   316  									Behaviors: []RuleBehavior{},
   317  									Criteria: []RuleBehavior{
   318  										{
   319  											Locked: false,
   320  											Name:   "contentType",
   321  											Options: RuleOptionsMap{
   322  												"matchOperator":      "IS_ONE_OF",
   323  												"matchWildcard":      true,
   324  												"matchCaseSensitive": false,
   325  												"values":             []interface{}{"text/html*", "text/css*", "application/x-javascript*"},
   326  											},
   327  										},
   328  									},
   329  									Name: "Compress Text Content"},
   330  							}}},
   331  					Name:    "default",
   332  					Options: RuleOptions{IsSecure: false},
   333  					CustomOverride: &RuleCustomOverride{
   334  						OverrideID: "cbo_12345",
   335  						Name:       "mdc",
   336  					},
   337  					Variables: []RuleVariable{
   338  						{
   339  							Description: tools.StringPtr("This is a sample Property Manager variable."),
   340  							Hidden:      false,
   341  							Name:        "VAR_NAME",
   342  							Sensitive:   false,
   343  							Value:       tools.StringPtr("default value"),
   344  						},
   345  					},
   346  				},
   347  			},
   348  		},
   349  		"200 OK nested with empty name": {
   350  			params: GetRuleTreeRequest{
   351  				PropertyID:      "propertyID",
   352  				PropertyVersion: 2,
   353  				ContractID:      "contract",
   354  				GroupID:         "group",
   355  				ValidateMode:    "fast",
   356  				ValidateRules:   false,
   357  			},
   358  			responseStatus: http.StatusOK,
   359  			responseBody: `
   360  {
   361  	"accountId": "accountID",
   362  	"contractId": "contract",
   363  	"groupId": "group",
   364  	"propertyId": "propertyID",
   365  	"propertyVersion": 2,
   366  	"etag": "etag",
   367  	"ruleFormat": "v2020-09-16",
   368  	"rules": {
   369  		"name": "default",
   370  		"children": [{
   371  			"criteria": [],
   372  			"children": [{
   373  				"name": "Compress Text Content",
   374  				"criteria": [{
   375  					"name": "contentType",
   376  					"options": {
   377  						"matchOperator": "IS_ONE_OF",
   378  						"matchWildcard": true,
   379  						"matchCaseSensitive": false,
   380  						"values": [
   381  							"text/html*",
   382  							"text/css*",
   383  							"application/x-javascript*"
   384  						]
   385  					}
   386  				}],
   387  				"behaviors": []
   388  			}],
   389  			"options": {
   390  				"is_secure": false
   391  			}
   392  		}],
   393  		"behaviors": [{
   394  				"name": "origin",
   395  				"options": {
   396  					"httpPort": 80,
   397  					"enableTrueClientIp": false,
   398  					"compress": true,
   399  					"cacheKeyHostname": "ORIGIN_HOSTNAME",
   400  					"forwardHostHeader": "REQUEST_HOST_HEADER",
   401  					"hostname": "origin.test.com",
   402  					"originType": "CUSTOMER"
   403  				}
   404  			},
   405  			{
   406  				"name": "cpCode",
   407  				"options": {
   408  					"value": {
   409  						"id": 12345,
   410  						"name": "my CP code"
   411  					}
   412  				}
   413  			}
   414  		],
   415  		"customOverride": {
   416  			"overrideId": "cbo_12345",
   417  			"name": "mdc"
   418  		},
   419  		"variables": [{
   420  			"name": "VAR_NAME",
   421  			"value": "default value",
   422  			"description": "This is a sample Property Manager variable.",
   423  			"hidden": false,
   424  			"sensitive": false
   425  		}]
   426  	}
   427  }`,
   428  			expectedPath: "/papi/v1/properties/propertyID/versions/2/rules?contractId=contract&groupId=group&validateMode=fast&validateRules=false",
   429  			expectedResponse: &GetRuleTreeResponse{
   430  				Response: Response{
   431  					AccountID:  "accountID",
   432  					ContractID: "contract",
   433  					GroupID:    "group",
   434  				},
   435  				PropertyID:      "propertyID",
   436  				PropertyVersion: 2,
   437  				Etag:            "etag",
   438  				RuleFormat:      "v2020-09-16",
   439  				Rules: Rules{
   440  					Behaviors: []RuleBehavior{
   441  						{
   442  							Name: "origin",
   443  							Options: RuleOptionsMap{
   444  								"httpPort":           float64(80),
   445  								"enableTrueClientIp": false,
   446  								"compress":           true,
   447  								"cacheKeyHostname":   "ORIGIN_HOSTNAME",
   448  								"forwardHostHeader":  "REQUEST_HOST_HEADER",
   449  								"hostname":           "origin.test.com",
   450  								"originType":         "CUSTOMER",
   451  							},
   452  						},
   453  						{
   454  							Name: "cpCode",
   455  							Options: RuleOptionsMap{
   456  								"value": map[string]interface{}{
   457  									"id":   float64(12345),
   458  									"name": "my CP code",
   459  								},
   460  							},
   461  						},
   462  					},
   463  					Children: []Rules{
   464  						{
   465  							Criteria: []RuleBehavior{},
   466  							Children: []Rules{
   467  								{
   468  									Behaviors: []RuleBehavior{},
   469  									Criteria: []RuleBehavior{
   470  										{
   471  											Locked: false,
   472  											Name:   "contentType",
   473  											Options: RuleOptionsMap{
   474  												"matchOperator":      "IS_ONE_OF",
   475  												"matchWildcard":      true,
   476  												"matchCaseSensitive": false,
   477  												"values":             []interface{}{"text/html*", "text/css*", "application/x-javascript*"},
   478  											},
   479  										},
   480  									},
   481  									Name: "Compress Text Content"},
   482  							}}},
   483  					Name:    "default",
   484  					Options: RuleOptions{IsSecure: false},
   485  					CustomOverride: &RuleCustomOverride{
   486  						OverrideID: "cbo_12345",
   487  						Name:       "mdc",
   488  					},
   489  					Variables: []RuleVariable{
   490  						{
   491  							Description: tools.StringPtr("This is a sample Property Manager variable."),
   492  							Hidden:      false,
   493  							Name:        "VAR_NAME",
   494  							Sensitive:   false,
   495  							Value:       tools.StringPtr("default value"),
   496  						},
   497  					},
   498  				},
   499  			},
   500  		},
   501  		"500 Internal Server Error": {
   502  			params: GetRuleTreeRequest{
   503  				PropertyID:      "propertyID",
   504  				PropertyVersion: 2,
   505  				ContractID:      "contract",
   506  				GroupID:         "group",
   507  				ValidateMode:    "fast",
   508  				ValidateRules:   false,
   509  			},
   510  			responseStatus: http.StatusInternalServerError,
   511  			responseBody: `
   512  {
   513  	"type": "internal_error",
   514      "title": "Internal Server Error",
   515      "detail": "Error fetching rule tree",
   516      "status": 500
   517  }`,
   518  			expectedPath: "/papi/v1/properties/propertyID/versions/2/rules?contractId=contract&groupId=group&validateMode=fast&validateRules=false",
   519  			withError: func(t *testing.T, err error) {
   520  				want := &Error{
   521  					Type:       "internal_error",
   522  					Title:      "Internal Server Error",
   523  					Detail:     "Error fetching rule tree",
   524  					StatusCode: http.StatusInternalServerError,
   525  				}
   526  				assert.True(t, errors.Is(err, want), "want: %s; got: %s", want, err)
   527  			},
   528  		},
   529  		"empty property ID": {
   530  			params: GetRuleTreeRequest{
   531  				PropertyID:      "",
   532  				PropertyVersion: 2,
   533  				ContractID:      "contract",
   534  				GroupID:         "group",
   535  				ValidateMode:    "fast",
   536  				ValidateRules:   false,
   537  			},
   538  			withError: func(t *testing.T, err error) {
   539  				want := ErrStructValidation
   540  				assert.True(t, errors.Is(err, want), "want: %s; got: %s", want, err)
   541  				assert.Contains(t, err.Error(), "PropertyID")
   542  			},
   543  		},
   544  		"empty property version": {
   545  			params: GetRuleTreeRequest{
   546  				PropertyID:    "propertyID",
   547  				ContractID:    "contract",
   548  				GroupID:       "group",
   549  				ValidateMode:  "fast",
   550  				ValidateRules: false,
   551  			},
   552  			withError: func(t *testing.T, err error) {
   553  				want := ErrStructValidation
   554  				assert.True(t, errors.Is(err, want), "want: %s; got: %s", want, err)
   555  				assert.Contains(t, err.Error(), "PropertyVersion")
   556  			},
   557  		},
   558  		"invalid validation mode": {
   559  			params: GetRuleTreeRequest{
   560  				PropertyID:      "propertyID",
   561  				PropertyVersion: 2,
   562  				ContractID:      "contract",
   563  				GroupID:         "group",
   564  				ValidateMode:    "test",
   565  				ValidateRules:   false,
   566  			},
   567  			withError: func(t *testing.T, err error) {
   568  				want := ErrStructValidation
   569  				assert.True(t, errors.Is(err, want), "want: %s; got: %s", want, err)
   570  				assert.Contains(t, err.Error(), "ValidateMode")
   571  			},
   572  		},
   573  		"Accept header with latest ruleFormat": {
   574  			params: GetRuleTreeRequest{
   575  				PropertyID:      "1",
   576  				PropertyVersion: 2,
   577  				ContractID:      "contract",
   578  				GroupID:         "group",
   579  				ValidateMode:    "fast",
   580  				ValidateRules:   false,
   581  				RuleFormat:      "latest",
   582  			},
   583  			responseStatus:   http.StatusOK,
   584  			responseBody:     "{}",
   585  			expectedPath:     "/papi/v1/properties/1/versions/2/rules?contractId=contract&groupId=group&validateMode=fast&validateRules=false",
   586  			expectedResponse: &GetRuleTreeResponse{},
   587  		},
   588  		"Accept header with versioned ruleFormat": {
   589  			params: GetRuleTreeRequest{
   590  				PropertyID:      "1",
   591  				PropertyVersion: 2,
   592  				ContractID:      "contract",
   593  				GroupID:         "group",
   594  				ValidateMode:    "fast",
   595  				ValidateRules:   false,
   596  				RuleFormat:      "v2021-01-01",
   597  			},
   598  			responseStatus:   http.StatusOK,
   599  			responseBody:     "{}",
   600  			expectedPath:     "/papi/v1/properties/1/versions/2/rules?contractId=contract&groupId=group&validateMode=fast&validateRules=false",
   601  			expectedResponse: &GetRuleTreeResponse{},
   602  		},
   603  		"Accept header with invalid ruleFormat": {
   604  			params: GetRuleTreeRequest{
   605  				PropertyID:      "1",
   606  				PropertyVersion: 2,
   607  				ContractID:      "contract",
   608  				GroupID:         "group",
   609  				ValidateMode:    "fast",
   610  				ValidateRules:   false,
   611  				RuleFormat:      "invalid",
   612  			},
   613  			responseStatus: http.StatusUnsupportedMediaType,
   614  			withError: func(t *testing.T, err error) {
   615  				want := ErrStructValidation
   616  				assert.True(t, errors.Is(err, want), "want: %s; got: %s", want, err)
   617  				assert.Contains(t, err.Error(), "RuleFormat")
   618  			},
   619  		},
   620  	}
   621  
   622  	for name, test := range tests {
   623  		t.Run(name, func(t *testing.T) {
   624  			mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   625  				assert.Equal(t, test.expectedPath, r.URL.String())
   626  				assert.Equal(t, http.MethodGet, r.Method)
   627  				w.WriteHeader(test.responseStatus)
   628  				_, err := w.Write([]byte(test.responseBody))
   629  				if test.params.RuleFormat != "" {
   630  					assert.Equal(t, r.Header.Get("Accept"), fmt.Sprintf("application/vnd.akamai.papirules.%s+json", test.params.RuleFormat))
   631  				}
   632  				assert.NoError(t, err)
   633  			}))
   634  			client := mockAPIClient(t, mockServer)
   635  			result, err := client.GetRuleTree(context.Background(), test.params)
   636  			if test.withError != nil {
   637  				test.withError(t, err)
   638  				return
   639  			}
   640  			require.NoError(t, err)
   641  			assert.Equal(t, test.expectedResponse, result)
   642  		})
   643  	}
   644  }
   645  
   646  func TestPapi_UpdateRuleTree(t *testing.T) {
   647  	tests := map[string]struct {
   648  		params           UpdateRulesRequest
   649  		requestBody      string
   650  		responseStatus   int
   651  		responseBody     string
   652  		expectedPath     string
   653  		expectedResponse *UpdateRulesResponse
   654  		withError        func(*testing.T, error)
   655  	}{
   656  		"200 OK": {
   657  			params: UpdateRulesRequest{
   658  				PropertyID:      "propertyID",
   659  				PropertyVersion: 2,
   660  				ContractID:      "contract",
   661  				GroupID:         "group",
   662  				DryRun:          true,
   663  				ValidateMode:    "fast",
   664  				ValidateRules:   false,
   665  				Rules: RulesUpdate{
   666  					Comments: "version comment",
   667  					Rules: Rules{
   668  						Comments: "default comment",
   669  						Behaviors: []RuleBehavior{
   670  							{
   671  								Name: "origin",
   672  								Options: RuleOptionsMap{
   673  									"httpPort":           float64(80),
   674  									"enableTrueClientIp": false,
   675  									"compress":           true,
   676  									"cacheKeyHostname":   "ORIGIN_HOSTNAME",
   677  									"forwardHostHeader":  "REQUEST_HOST_HEADER",
   678  									"hostname":           "origin.test.com",
   679  									"originType":         "CUSTOMER",
   680  								},
   681  							},
   682  							{
   683  								Name: "cpCode",
   684  								Options: RuleOptionsMap{
   685  									"value": map[string]interface{}{
   686  										"id":   float64(12345),
   687  										"name": "my CP code",
   688  									},
   689  								},
   690  							},
   691  						},
   692  						Children: []Rules{
   693  							{
   694  								Behaviors: []RuleBehavior{
   695  									{
   696  										Name: "gzipResponse",
   697  										Options: RuleOptionsMap{
   698  											"behavior": "ALWAYS",
   699  										},
   700  									},
   701  								},
   702  								Criteria: []RuleBehavior{
   703  									{
   704  										Locked: false,
   705  										Name:   "contentType",
   706  										Options: RuleOptionsMap{
   707  											"matchOperator":      "IS_ONE_OF",
   708  											"matchWildcard":      true,
   709  											"matchCaseSensitive": false,
   710  											"values":             []interface{}{"text/html*", "text/css*", "application/x-javascript*"},
   711  										},
   712  									},
   713  								},
   714  								Name: "Compress Text Content",
   715  							},
   716  						},
   717  						Criteria: []RuleBehavior{},
   718  						Name:     "default",
   719  						Options:  RuleOptions{IsSecure: false},
   720  						CustomOverride: &RuleCustomOverride{
   721  							OverrideID: "cbo_12345",
   722  							Name:       "mdc",
   723  						},
   724  						Variables: []RuleVariable{
   725  							{
   726  								Description: tools.StringPtr("This is a sample Property Manager variable."),
   727  								Hidden:      false,
   728  								Name:        "VAR_NAME",
   729  								Sensitive:   false,
   730  								Value:       tools.StringPtr("default value"),
   731  							},
   732  						},
   733  					},
   734  				},
   735  			},
   736  			responseStatus: http.StatusOK,
   737  			responseBody: `
   738  {
   739      "accountId": "accountID",
   740      "contractId": "contract",
   741      "groupId": "group",
   742      "propertyId": "propertyID",
   743      "propertyVersion": 2,
   744      "etag": "etag",
   745      "ruleFormat": "v2020-09-16",
   746  	"comments": "version comment",
   747      "rules": {
   748          "name": "default",
   749          "comments": "default comment",
   750          "criteria": [],
   751          "children": [
   752              {
   753                  "name": "Compress Text Content",
   754                  "criteria": [
   755                      {
   756                          "name": "contentType",
   757                          "options": {
   758                              "matchOperator": "IS_ONE_OF",
   759                              "matchWildcard": true,
   760                              "matchCaseSensitive": false,
   761                              "values": [
   762                                  "text/html*",
   763                                  "text/css*",
   764                                  "application/x-javascript*"
   765                              ]
   766                          }
   767                      }
   768                  ],
   769                  "behaviors": [
   770                      {
   771                          "name": "gzipResponse",
   772                          "options": { "behavior": "ALWAYS" }
   773                      }
   774                  ]
   775              }
   776          ],
   777          "options": {
   778              "is_secure": false
   779          },
   780          "behaviors": [
   781              {
   782                  "name": "origin",
   783                  "options": {
   784                      "httpPort": 80,
   785                      "enableTrueClientIp": false,
   786                      "compress": true,
   787                      "cacheKeyHostname": "ORIGIN_HOSTNAME",
   788                      "forwardHostHeader": "REQUEST_HOST_HEADER",
   789                      "hostname": "origin.test.com",
   790                      "originType": "CUSTOMER"
   791                  }
   792              },
   793              {
   794                  "name": "cpCode",
   795                  "options": {
   796                      "value": {
   797                          "id": 12345,
   798                          "name": "my CP code"
   799                      }
   800                  }
   801              },
   802   			{
   803                  "name": "matchAdvanced",
   804                  "options": {
   805                      "value": {
   806                          "id": 12345,
   807                          "name": "my advanced match"
   808                      }
   809                  },
   810  				"locked": true,
   811  				"uuid": "fd6a63bc-120a-4891-a5f2-c479765d5553",
   812  				"templateUuid": "bedbac99-4ce1-43a3-96cc-b84c8cd30176"
   813              }
   814          ],
   815   		"customOverride": {
   816          	"overrideId": "cbo_12345",
   817          	"name": "mdc"
   818      	},
   819  		"templateUuid": "bedbac99-4ce1-43a3-96cc-b84c8cd30176",
   820  		"templateLink": "/platformtoolkit/service/ruletemplate/30582260/1?accountId=1-1TJZFB&gid=61726&ck=16.3.1.1",
   821  		"variables": [
   822              {
   823                  "name": "VAR_NAME",
   824                  "value": "default value",
   825                  "description": "This is a sample Property Manager variable.",
   826                  "hidden": false,
   827                  "sensitive": false
   828              }
   829          ]
   830      },
   831  	"errors": [
   832          {
   833              "type": "/papi/v1/errors/validation.required_behavior",
   834              "title": "Missing required behavior in default rule",
   835              "detail": "In order for this property to work correctly behavior Content Provider Code needs to be present in the default section",
   836              "instance": "/papi/v1/properties/prp_173136/versions/3/rules#err_100",
   837              "behaviorName": "cpCode"
   838          }
   839      ]
   840  }`,
   841  			expectedPath: "/papi/v1/properties/propertyID/versions/2/rules?contractId=contract&dryRun=true&groupId=group&validateMode=fast&validateRules=false",
   842  			expectedResponse: &UpdateRulesResponse{
   843  				AccountID:       "accountID",
   844  				ContractID:      "contract",
   845  				GroupID:         "group",
   846  				PropertyID:      "propertyID",
   847  				PropertyVersion: 2,
   848  				Etag:            "etag",
   849  				RuleFormat:      "v2020-09-16",
   850  				Comments:        "version comment",
   851  				Rules: Rules{
   852  					Comments: "default comment",
   853  					Behaviors: []RuleBehavior{
   854  						{
   855  							Name: "origin",
   856  							Options: RuleOptionsMap{
   857  								"httpPort":           float64(80),
   858  								"enableTrueClientIp": false,
   859  								"compress":           true,
   860  								"cacheKeyHostname":   "ORIGIN_HOSTNAME",
   861  								"forwardHostHeader":  "REQUEST_HOST_HEADER",
   862  								"hostname":           "origin.test.com",
   863  								"originType":         "CUSTOMER",
   864  							},
   865  						},
   866  						{
   867  							Name: "cpCode",
   868  							Options: RuleOptionsMap{
   869  								"value": map[string]interface{}{
   870  									"id":   float64(12345),
   871  									"name": "my CP code",
   872  								},
   873  							},
   874  						},
   875  						{
   876  							Name: "matchAdvanced",
   877  							Options: RuleOptionsMap{
   878  								"value": map[string]interface{}{
   879  									"id":   float64(12345),
   880  									"name": "my advanced match",
   881  								},
   882  							},
   883  							Locked:       true,
   884  							UUID:         "fd6a63bc-120a-4891-a5f2-c479765d5553",
   885  							TemplateUuid: "bedbac99-4ce1-43a3-96cc-b84c8cd30176",
   886  						},
   887  					},
   888  					Children: []Rules{
   889  						{
   890  							Behaviors: []RuleBehavior{
   891  								{
   892  									Name: "gzipResponse",
   893  									Options: RuleOptionsMap{
   894  										"behavior": "ALWAYS",
   895  									},
   896  								},
   897  							},
   898  							Criteria: []RuleBehavior{
   899  								{
   900  									Locked: false,
   901  									Name:   "contentType",
   902  									Options: RuleOptionsMap{
   903  										"matchOperator":      "IS_ONE_OF",
   904  										"matchWildcard":      true,
   905  										"matchCaseSensitive": false,
   906  										"values":             []interface{}{"text/html*", "text/css*", "application/x-javascript*"},
   907  									},
   908  								},
   909  							},
   910  							Name: "Compress Text Content",
   911  						},
   912  					},
   913  					Criteria: []RuleBehavior{},
   914  					Name:     "default",
   915  					Options:  RuleOptions{IsSecure: false},
   916  					CustomOverride: &RuleCustomOverride{
   917  						OverrideID: "cbo_12345",
   918  						Name:       "mdc",
   919  					},
   920  					TemplateUuid: "bedbac99-4ce1-43a3-96cc-b84c8cd30176",
   921  					TemplateLink: "/platformtoolkit/service/ruletemplate/30582260/1?accountId=1-1TJZFB&gid=61726&ck=16.3.1.1",
   922  					Variables: []RuleVariable{
   923  						{
   924  							Description: tools.StringPtr("This is a sample Property Manager variable."),
   925  							Hidden:      false,
   926  							Name:        "VAR_NAME",
   927  							Sensitive:   false,
   928  							Value:       tools.StringPtr("default value"),
   929  						},
   930  					},
   931  				},
   932  				Errors: []RuleError{
   933  					{
   934  						Type:         "/papi/v1/errors/validation.required_behavior",
   935  						Title:        "Missing required behavior in default rule",
   936  						Detail:       "In order for this property to work correctly behavior Content Provider Code needs to be present in the default section",
   937  						Instance:     "/papi/v1/properties/prp_173136/versions/3/rules#err_100",
   938  						BehaviorName: "cpCode",
   939  					},
   940  				},
   941  			},
   942  		},
   943  		"200 OK - empty and null values for description": {
   944  			params: UpdateRulesRequest{
   945  				ContractID:      "ctr_id",
   946  				GroupID:         "grp_id",
   947  				PropertyID:      "prp_id",
   948  				PropertyVersion: 1,
   949  				Rules: RulesUpdate{
   950  					Comments: "version comment",
   951  					Rules: Rules{
   952  						Name: "default",
   953  						Children: []Rules{
   954  							{
   955  								Name:     "change fwd path",
   956  								Children: []Rules{},
   957  								Behaviors: []RuleBehavior{
   958  									{
   959  										Name: "baseDirectory",
   960  										Options: RuleOptionsMap{
   961  											"value": "/smth/",
   962  										},
   963  									},
   964  								},
   965  								Criteria: []RuleBehavior{
   966  									{
   967  										Name:   "requestHeader",
   968  										Locked: false,
   969  										Options: RuleOptionsMap{
   970  											"headerName":              "Accept-Encoding",
   971  											"matchCaseSensitiveValue": true,
   972  											"matchOperator":           "IS_ONE_OF",
   973  											"matchWildcardName":       false,
   974  											"matchWildcardValue":      false,
   975  										},
   976  									},
   977  								},
   978  								CriteriaMustSatisfy: RuleCriteriaMustSatisfyAll,
   979  							},
   980  							{
   981  								Name:     "caching",
   982  								Children: []Rules{},
   983  								Behaviors: []RuleBehavior{
   984  									{
   985  										Name: "caching",
   986  										Options: RuleOptionsMap{
   987  											"behavior":       "MAX_AGE",
   988  											"mustRevalidate": false,
   989  											"ttl":            "1m",
   990  										},
   991  									},
   992  								},
   993  								Criteria:            []RuleBehavior{},
   994  								CriteriaMustSatisfy: RuleCriteriaMustSatisfyAny,
   995  							},
   996  						},
   997  						Behaviors: []RuleBehavior{
   998  							{
   999  								Name: "origin",
  1000  								Options: RuleOptionsMap{
  1001  									"cacheKeyHostname":          "REQUEST_HOST_HEADER",
  1002  									"compress":                  true,
  1003  									"enableTrueClientIp":        true,
  1004  									"forwardHostHeader":         "REQUEST_HOST_HEADER",
  1005  									"hostname":                  "httpbin.smth.online",
  1006  									"httpPort":                  float64(80),
  1007  									"httpsPort":                 float64(443),
  1008  									"originCertificate":         "",
  1009  									"originSni":                 true,
  1010  									"originType":                "CUSTOMER",
  1011  									"ports":                     "",
  1012  									"trueClientIpClientSetting": false,
  1013  									"trueClientIpHeader":        "True-Client-IP",
  1014  									"verificationMode":          "PLATFORM_SETTINGS",
  1015  								},
  1016  							},
  1017  						},
  1018  						Options:  RuleOptions{},
  1019  						Criteria: []RuleBehavior{},
  1020  						Variables: []RuleVariable{
  1021  							{
  1022  								Name:        "TEST_EMPTY_FIELDS",
  1023  								Value:       tools.StringPtr(""),
  1024  								Description: tools.StringPtr(""),
  1025  								Hidden:      true,
  1026  								Sensitive:   false,
  1027  							},
  1028  							{
  1029  								Name:        "TEST_NIL_DESCRIPTION",
  1030  								Description: nil,
  1031  								Value:       tools.StringPtr(""),
  1032  								Hidden:      true,
  1033  								Sensitive:   false,
  1034  							},
  1035  						},
  1036  						Comments: "The behaviors in the Default Rule apply to all requests for the property hostname(s) unless another rule overrides the Default Rule settings.",
  1037  					},
  1038  				},
  1039  			},
  1040  			requestBody:    `{"comments":"version comment","rules":{"behaviors":[{"name":"origin","options":{"cacheKeyHostname":"REQUEST_HOST_HEADER","compress":true,"enableTrueClientIp":true,"forwardHostHeader":"REQUEST_HOST_HEADER","hostname":"httpbin.smth.online","httpPort":80,"httpsPort":443,"originCertificate":"","originSni":true,"originType":"CUSTOMER","ports":"","trueClientIpClientSetting":false,"trueClientIpHeader":"True-Client-IP","verificationMode":"PLATFORM_SETTINGS"}}],"children":[{"behaviors":[{"name":"baseDirectory","options":{"value":"/smth/"}}],"criteria":[{"name":"requestHeader","options":{"headerName":"Accept-Encoding","matchCaseSensitiveValue":true,"matchOperator":"IS_ONE_OF","matchWildcardName":false,"matchWildcardValue":false}}],"name":"change fwd path","options":{},"criteriaMustSatisfy":"all"},{"behaviors":[{"name":"caching","options":{"behavior":"MAX_AGE","mustRevalidate":false,"ttl":"1m"}}],"name":"caching","options":{},"criteriaMustSatisfy":"any"}],"comments":"The behaviors in the Default Rule apply to all requests for the property hostname(s) unless another rule overrides the Default Rule settings.","name":"default","options":{},"variables":[{"description":"","hidden":true,"name":"TEST_EMPTY_FIELDS","sensitive":false,"value":""},{"description":null,"hidden":true,"name":"TEST_NIL_DESCRIPTION","sensitive":false,"value":""}]}}`,
  1041  			responseStatus: http.StatusOK,
  1042  			responseBody: `{
  1043      "accountId": "act_id",
  1044      "contractId": "ctr_id",
  1045      "groupId": "grp_id",
  1046      "propertyId": "grp_id",
  1047      "propertyName": "dxe-2407-reproducing",
  1048      "propertyVersion": 1,
  1049      "etag": "123ge123",
  1050      "rules": {
  1051          "name": "default",
  1052          "children": [
  1053              {
  1054                  "name": "change fwd path",
  1055                  "children": [],
  1056                  "behaviors": [
  1057                      {
  1058                          "name": "baseDirectory",
  1059                          "options": {
  1060                              "value": "/smth/"
  1061                          }
  1062                      }
  1063                  ],
  1064                  "criteria": [
  1065                      {
  1066                          "name": "requestHeader",
  1067                          "options": {
  1068                              "headerName": "Accept-Encoding",
  1069                              "matchCaseSensitiveValue": true,
  1070                              "matchOperator": "IS_ONE_OF",
  1071                              "matchWildcardName": false,
  1072                              "matchWildcardValue": false
  1073                          }
  1074                      }
  1075                  ],
  1076                  "criteriaMustSatisfy": "all"
  1077              },
  1078              {
  1079                  "name": "caching",
  1080                  "children": [],
  1081                  "behaviors": [
  1082                      {
  1083                          "name": "caching",
  1084                          "options": {
  1085                              "behavior": "MAX_AGE",
  1086                              "mustRevalidate": false,
  1087                              "ttl": "1m"
  1088                          }
  1089                      }
  1090                  ],
  1091                  "criteria": [],
  1092                  "criteriaMustSatisfy": "any"
  1093              }
  1094          ],
  1095          "behaviors": [
  1096              {
  1097                  "name": "origin",
  1098                  "options": {
  1099                      "cacheKeyHostname": "REQUEST_HOST_HEADER",
  1100                      "compress": true,
  1101                      "enableTrueClientIp": true,
  1102                      "forwardHostHeader": "REQUEST_HOST_HEADER",
  1103                      "hostname": "httpbin.smth.online",
  1104                      "httpPort": 80,
  1105                      "httpsPort": 443,
  1106                      "originCertificate": "",
  1107                      "originSni": true,
  1108                      "originType": "CUSTOMER",
  1109                      "ports": "",
  1110                      "trueClientIpClientSetting": false,
  1111                      "trueClientIpHeader": "True-Client-IP",
  1112                      "verificationMode": "PLATFORM_SETTINGS"
  1113                  }
  1114              }
  1115          ],
  1116          "options": {},
  1117          "variables": [
  1118              {
  1119                  "name": "TEST_EMPTY_FIELDS",
  1120                  "value": "",
  1121                  "description": "",
  1122                  "hidden": true,
  1123                  "sensitive": false
  1124              },
  1125              {
  1126                  "name": "TEST_NIL_FIELDS",
  1127                  "value": "",
  1128                  "description": null,
  1129                  "hidden": true,
  1130                  "sensitive": false
  1131              }
  1132          ],
  1133          "comments": "The behaviors in the Default Rule apply to all requests for the property hostname(s) unless another rule overrides the Default Rule settings."
  1134      },
  1135      "errors": [
  1136          {
  1137              "type": "https://problems.luna.akamaiapis.net/papi/v0/validation/required_feature_any",
  1138              "errorLocation": "#/rules",
  1139              "detail": "Add a Content Provider Code behavior in any rule. See <a href=\"/dl/property-manager/property-manager-help/csh_lookup.html?id=PM_0030\" target=\"_blank\">Content Provider Code</a>."
  1140          }
  1141      ],
  1142      "warnings": [
  1143          {
  1144              "type": "https://problems.luna.akamaiapis.net/papi/v0/validation/incompatible_condition",
  1145              "errorLocation": "#/rules/children/0/behaviors/0"
  1146          },
  1147          {
  1148              "title": "Unstable rule format",
  1149              "type": "https://problems.luna.akamaiapis.net/papi/v0/unstable_rule_format",
  1150              "currentRuleFormat": "latest",
  1151              "suggestedRuleFormat": "v2023-01-05"
  1152          }
  1153      ],
  1154      "ruleFormat": "latest"
  1155  }`,
  1156  			expectedPath: "/papi/v1/properties/prp_id/versions/1/rules?contractId=ctr_id&groupId=grp_id&validateRules=false",
  1157  			expectedResponse: &UpdateRulesResponse{
  1158  				AccountID:       "act_id",
  1159  				ContractID:      "ctr_id",
  1160  				GroupID:         "grp_id",
  1161  				PropertyID:      "grp_id",
  1162  				PropertyVersion: 1,
  1163  				Etag:            "123ge123",
  1164  				RuleFormat:      "latest",
  1165  				Rules: Rules{
  1166  					Name: "default",
  1167  					Children: []Rules{
  1168  						{
  1169  							Name:     "change fwd path",
  1170  							Children: []Rules{},
  1171  							Behaviors: []RuleBehavior{
  1172  								{
  1173  									Name: "baseDirectory",
  1174  									Options: RuleOptionsMap{
  1175  										"value": "/smth/",
  1176  									},
  1177  								},
  1178  							},
  1179  							Criteria: []RuleBehavior{
  1180  								{
  1181  									Name:   "requestHeader",
  1182  									Locked: false,
  1183  									Options: RuleOptionsMap{
  1184  										"headerName":              "Accept-Encoding",
  1185  										"matchCaseSensitiveValue": true,
  1186  										"matchOperator":           "IS_ONE_OF",
  1187  										"matchWildcardName":       false,
  1188  										"matchWildcardValue":      false,
  1189  									},
  1190  								},
  1191  							},
  1192  							CriteriaMustSatisfy: "all",
  1193  						},
  1194  						{
  1195  							Name:     "caching",
  1196  							Children: []Rules{},
  1197  							Behaviors: []RuleBehavior{
  1198  								{
  1199  									Name: "caching",
  1200  									Options: RuleOptionsMap{
  1201  										"behavior":       "MAX_AGE",
  1202  										"mustRevalidate": false,
  1203  										"ttl":            "1m",
  1204  									},
  1205  								},
  1206  							},
  1207  							Criteria:            []RuleBehavior{},
  1208  							CriteriaMustSatisfy: "any",
  1209  						},
  1210  					},
  1211  					Behaviors: []RuleBehavior{
  1212  						{
  1213  							Name: "origin",
  1214  							Options: RuleOptionsMap{
  1215  								"cacheKeyHostname":          "REQUEST_HOST_HEADER",
  1216  								"compress":                  true,
  1217  								"enableTrueClientIp":        true,
  1218  								"forwardHostHeader":         "REQUEST_HOST_HEADER",
  1219  								"hostname":                  "httpbin.smth.online",
  1220  								"httpPort":                  float64(80),
  1221  								"httpsPort":                 float64(443),
  1222  								"originCertificate":         "",
  1223  								"originSni":                 true,
  1224  								"originType":                "CUSTOMER",
  1225  								"ports":                     "",
  1226  								"trueClientIpClientSetting": false,
  1227  								"trueClientIpHeader":        "True-Client-IP",
  1228  								"verificationMode":          "PLATFORM_SETTINGS",
  1229  							},
  1230  						},
  1231  					},
  1232  					Variables: []RuleVariable{
  1233  						{
  1234  							Name:        "TEST_EMPTY_FIELDS",
  1235  							Value:       tools.StringPtr(""),
  1236  							Description: tools.StringPtr(""),
  1237  							Hidden:      true,
  1238  							Sensitive:   false,
  1239  						},
  1240  						{
  1241  							Name:        "TEST_NIL_FIELDS",
  1242  							Description: nil,
  1243  							Value:       tools.StringPtr(""),
  1244  							Hidden:      true,
  1245  							Sensitive:   false,
  1246  						},
  1247  					},
  1248  					Comments: "The behaviors in the Default Rule apply to all requests for the property hostname(s) unless another rule overrides the Default Rule settings.",
  1249  				},
  1250  				Errors: []RuleError{
  1251  					{
  1252  						Type:          "https://problems.luna.akamaiapis.net/papi/v0/validation/required_feature_any",
  1253  						ErrorLocation: "#/rules",
  1254  						Detail:        "Add a Content Provider Code behavior in any rule. See <a href=\"/dl/property-manager/property-manager-help/csh_lookup.html?id=PM_0030\" target=\"_blank\">Content Provider Code</a>.",
  1255  					},
  1256  				},
  1257  				Warnings: []RuleWarnings{
  1258  					{
  1259  						Type:          "https://problems.luna.akamaiapis.net/papi/v0/validation/incompatible_condition",
  1260  						ErrorLocation: "#/rules/children/0/behaviors/0",
  1261  					},
  1262  					{
  1263  						Title:               "Unstable rule format",
  1264  						Type:                "https://problems.luna.akamaiapis.net/papi/v0/unstable_rule_format",
  1265  						CurrentRuleFormat:   "latest",
  1266  						SuggestedRuleFormat: "v2023-01-05",
  1267  					},
  1268  				},
  1269  			},
  1270  		},
  1271  		"validation error - value is null": {
  1272  			params: UpdateRulesRequest{
  1273  				ContractID:      "ctr_id",
  1274  				GroupID:         "grp_id",
  1275  				PropertyID:      "prp_id",
  1276  				PropertyVersion: 1,
  1277  				Rules: RulesUpdate{
  1278  					Comments: "version comment",
  1279  					Rules: Rules{
  1280  						Name: "default",
  1281  						Variables: []RuleVariable{
  1282  							{
  1283  								Name:        "TEST_EMPTY_FIELDS",
  1284  								Value:       tools.StringPtr(""),
  1285  								Description: tools.StringPtr(""),
  1286  								Hidden:      true,
  1287  								Sensitive:   false,
  1288  							},
  1289  							{
  1290  								Name:        "TEST_NIL_FIELDS",
  1291  								Description: nil,
  1292  								Value:       nil,
  1293  								Hidden:      true,
  1294  								Sensitive:   false,
  1295  							},
  1296  						},
  1297  						Comments: "The behaviors in the Default Rule apply to all requests for the property hostname(s) unless another rule overrides the Default Rule settings.",
  1298  					},
  1299  				},
  1300  			},
  1301  			expectedPath: "/papi/v1/properties/prp_id/versions/1/rules?contractId=ctr_id&groupId=grp_id&validateRules=false",
  1302  			withError: func(t *testing.T, err error) {
  1303  				want := ErrStructValidation
  1304  				assert.True(t, errors.Is(err, want), "want: %s; got: %s", want, err)
  1305  				assert.Contains(t, err.Error(), "updating rule tree: struct validation:\nVariables[1]: {\n\tValue: is required\n}")
  1306  			},
  1307  		},
  1308  		"500 Internal Server Error": {
  1309  			params: UpdateRulesRequest{
  1310  				PropertyID:      "propertyID",
  1311  				PropertyVersion: 2,
  1312  				ContractID:      "contract",
  1313  				GroupID:         "group",
  1314  				DryRun:          true,
  1315  				ValidateMode:    "fast",
  1316  				ValidateRules:   false,
  1317  				Rules: RulesUpdate{
  1318  					Rules: Rules{
  1319  						Behaviors: []RuleBehavior{
  1320  							{
  1321  								Name: "origin",
  1322  								Options: RuleOptionsMap{
  1323  									"httpPort":           float64(80),
  1324  									"enableTrueClientIp": false,
  1325  									"compress":           true,
  1326  									"cacheKeyHostname":   "ORIGIN_HOSTNAME",
  1327  									"forwardHostHeader":  "REQUEST_HOST_HEADER",
  1328  									"hostname":           "origin.test.com",
  1329  									"originType":         "CUSTOMER",
  1330  								},
  1331  							},
  1332  							{
  1333  								Name: "cpCode",
  1334  								Options: RuleOptionsMap{
  1335  									"value": map[string]interface{}{
  1336  										"id":   float64(12345),
  1337  										"name": "my CP code",
  1338  									},
  1339  								},
  1340  							},
  1341  						},
  1342  						Children: []Rules{
  1343  							{
  1344  								Behaviors: []RuleBehavior{
  1345  									{
  1346  										Name: "gzipResponse",
  1347  										Options: RuleOptionsMap{
  1348  											"behavior": "ALWAYS",
  1349  										},
  1350  									},
  1351  								},
  1352  								Criteria: []RuleBehavior{
  1353  									{
  1354  										Locked: false,
  1355  										Name:   "contentType",
  1356  										Options: RuleOptionsMap{
  1357  											"matchOperator":      "IS_ONE_OF",
  1358  											"matchWildcard":      true,
  1359  											"matchCaseSensitive": false,
  1360  											"values":             []interface{}{"text/html*", "text/css*", "application/x-javascript*"},
  1361  										},
  1362  									},
  1363  								},
  1364  								Name: "Compress Text Content",
  1365  							},
  1366  						},
  1367  						Criteria: []RuleBehavior{},
  1368  						Name:     "default",
  1369  						Options:  RuleOptions{IsSecure: false},
  1370  						CustomOverride: &RuleCustomOverride{
  1371  							OverrideID: "cbo_12345",
  1372  							Name:       "mdc",
  1373  						},
  1374  						Variables: []RuleVariable{
  1375  							{
  1376  								Description: tools.StringPtr("This is a sample Property Manager variable."),
  1377  								Hidden:      false,
  1378  								Name:        "VAR_NAME",
  1379  								Sensitive:   false,
  1380  								Value:       tools.StringPtr("default value"),
  1381  							},
  1382  						},
  1383  					},
  1384  				},
  1385  			},
  1386  			responseStatus: http.StatusInternalServerError,
  1387  			responseBody: `
  1388  {
  1389  	"type": "internal_error",
  1390      "title": "Internal Server Error",
  1391      "detail": "Error updating rule tree",
  1392      "status": 500
  1393  }`,
  1394  			expectedPath: "/papi/v1/properties/propertyID/versions/2/rules?contractId=contract&dryRun=true&groupId=group&validateMode=fast&validateRules=false",
  1395  			withError: func(t *testing.T, err error) {
  1396  				want := &Error{
  1397  					Type:       "internal_error",
  1398  					Title:      "Internal Server Error",
  1399  					Detail:     "Error updating rule tree",
  1400  					StatusCode: http.StatusInternalServerError,
  1401  				}
  1402  				assert.True(t, errors.Is(err, want), "want: %s; got: %s", want, err)
  1403  			},
  1404  		},
  1405  		"empty property ID": {
  1406  			params: UpdateRulesRequest{
  1407  				PropertyID:      "",
  1408  				PropertyVersion: 2,
  1409  				ContractID:      "contract",
  1410  				GroupID:         "group",
  1411  				DryRun:          true,
  1412  				ValidateMode:    "fast",
  1413  				ValidateRules:   false,
  1414  				Rules: RulesUpdate{
  1415  					Rules: Rules{
  1416  						Behaviors: []RuleBehavior{
  1417  							{
  1418  								Name: "origin",
  1419  								Options: RuleOptionsMap{
  1420  									"httpPort":           float64(80),
  1421  									"enableTrueClientIp": false,
  1422  									"compress":           true,
  1423  									"cacheKeyHostname":   "ORIGIN_HOSTNAME",
  1424  									"forwardHostHeader":  "REQUEST_HOST_HEADER",
  1425  									"hostname":           "origin.test.com",
  1426  									"originType":         "CUSTOMER",
  1427  								},
  1428  							},
  1429  							{
  1430  								Name: "cpCode",
  1431  								Options: RuleOptionsMap{
  1432  									"value": map[string]interface{}{
  1433  										"id":   float64(12345),
  1434  										"name": "my CP code",
  1435  									},
  1436  								},
  1437  							},
  1438  						},
  1439  						Children: []Rules{
  1440  							{
  1441  								Behaviors: []RuleBehavior{
  1442  									{
  1443  										Name: "gzipResponse",
  1444  										Options: RuleOptionsMap{
  1445  											"behavior": "ALWAYS",
  1446  										},
  1447  									},
  1448  								},
  1449  								Criteria: []RuleBehavior{
  1450  									{
  1451  										Locked: false,
  1452  										Name:   "contentType",
  1453  										Options: RuleOptionsMap{
  1454  											"matchOperator":      "IS_ONE_OF",
  1455  											"matchWildcard":      true,
  1456  											"matchCaseSensitive": false,
  1457  											"values":             []interface{}{"text/html*", "text/css*", "application/x-javascript*"},
  1458  										},
  1459  									},
  1460  								},
  1461  								Name: "Compress Text Content",
  1462  							},
  1463  						},
  1464  						Criteria: []RuleBehavior{},
  1465  						Name:     "default",
  1466  						Options:  RuleOptions{IsSecure: false},
  1467  						CustomOverride: &RuleCustomOverride{
  1468  							OverrideID: "cbo_12345",
  1469  							Name:       "mdc",
  1470  						},
  1471  						Variables: []RuleVariable{
  1472  							{
  1473  								Description: tools.StringPtr("This is a sample Property Manager variable."),
  1474  								Hidden:      false,
  1475  								Name:        "VAR_NAME",
  1476  								Sensitive:   false,
  1477  								Value:       tools.StringPtr("default value"),
  1478  							},
  1479  						},
  1480  					},
  1481  				},
  1482  			},
  1483  			withError: func(t *testing.T, err error) {
  1484  				want := ErrStructValidation
  1485  				assert.True(t, errors.Is(err, want), "want: %s; got: %s", want, err)
  1486  				assert.Contains(t, err.Error(), "PropertyID")
  1487  			},
  1488  		},
  1489  		"empty property version": {
  1490  			params: UpdateRulesRequest{
  1491  				PropertyID:    "propertyID",
  1492  				ContractID:    "contract",
  1493  				GroupID:       "group",
  1494  				DryRun:        true,
  1495  				ValidateMode:  "fast",
  1496  				ValidateRules: false,
  1497  				Rules: RulesUpdate{
  1498  					Rules: Rules{
  1499  						Behaviors: []RuleBehavior{
  1500  							{
  1501  								Name: "origin",
  1502  								Options: RuleOptionsMap{
  1503  									"httpPort":           float64(80),
  1504  									"enableTrueClientIp": false,
  1505  									"compress":           true,
  1506  									"cacheKeyHostname":   "ORIGIN_HOSTNAME",
  1507  									"forwardHostHeader":  "REQUEST_HOST_HEADER",
  1508  									"hostname":           "origin.test.com",
  1509  									"originType":         "CUSTOMER",
  1510  								},
  1511  							},
  1512  							{
  1513  								Name: "cpCode",
  1514  								Options: RuleOptionsMap{
  1515  									"value": map[string]interface{}{
  1516  										"id":   float64(12345),
  1517  										"name": "my CP code",
  1518  									},
  1519  								},
  1520  							},
  1521  						},
  1522  						Children: []Rules{
  1523  							{
  1524  								Behaviors: []RuleBehavior{
  1525  									{
  1526  										Name: "gzipResponse",
  1527  										Options: RuleOptionsMap{
  1528  											"behavior": "ALWAYS",
  1529  										},
  1530  									},
  1531  								},
  1532  								Criteria: []RuleBehavior{
  1533  									{
  1534  										Locked: false,
  1535  										Name:   "contentType",
  1536  										Options: RuleOptionsMap{
  1537  											"matchOperator":      "IS_ONE_OF",
  1538  											"matchWildcard":      true,
  1539  											"matchCaseSensitive": false,
  1540  											"values":             []interface{}{"text/html*", "text/css*", "application/x-javascript*"},
  1541  										},
  1542  									},
  1543  								},
  1544  								Name: "Compress Text Content",
  1545  							},
  1546  						},
  1547  						Criteria: []RuleBehavior{},
  1548  						Name:     "default",
  1549  						Options:  RuleOptions{IsSecure: false},
  1550  						CustomOverride: &RuleCustomOverride{
  1551  							OverrideID: "cbo_12345",
  1552  							Name:       "mdc",
  1553  						},
  1554  						Variables: []RuleVariable{
  1555  							{
  1556  								Description: tools.StringPtr("This is a sample Property Manager variable."),
  1557  								Hidden:      false,
  1558  								Name:        "VAR_NAME",
  1559  								Sensitive:   false,
  1560  								Value:       tools.StringPtr("default value"),
  1561  							},
  1562  						},
  1563  					},
  1564  				},
  1565  			},
  1566  			withError: func(t *testing.T, err error) {
  1567  				want := ErrStructValidation
  1568  				assert.True(t, errors.Is(err, want), "want: %s; got: %s", want, err)
  1569  				assert.Contains(t, err.Error(), "PropertyVersion")
  1570  			},
  1571  		},
  1572  		"invalid validation mode": {
  1573  			params: UpdateRulesRequest{
  1574  				PropertyID:      "propertyID",
  1575  				PropertyVersion: 2,
  1576  				ContractID:      "contract",
  1577  				GroupID:         "group",
  1578  				DryRun:          true,
  1579  				ValidateMode:    "test",
  1580  				ValidateRules:   false,
  1581  				Rules: RulesUpdate{
  1582  					Rules: Rules{
  1583  						Behaviors: []RuleBehavior{
  1584  							{
  1585  								Name: "origin",
  1586  								Options: RuleOptionsMap{
  1587  									"httpPort":           float64(80),
  1588  									"enableTrueClientIp": false,
  1589  									"compress":           true,
  1590  									"cacheKeyHostname":   "ORIGIN_HOSTNAME",
  1591  									"forwardHostHeader":  "REQUEST_HOST_HEADER",
  1592  									"hostname":           "origin.test.com",
  1593  									"originType":         "CUSTOMER",
  1594  								},
  1595  							},
  1596  							{
  1597  								Name: "cpCode",
  1598  								Options: RuleOptionsMap{
  1599  									"value": map[string]interface{}{
  1600  										"id":   float64(12345),
  1601  										"name": "my CP code",
  1602  									},
  1603  								},
  1604  							},
  1605  						},
  1606  						Children: []Rules{
  1607  							{
  1608  								Behaviors: []RuleBehavior{
  1609  									{
  1610  										Name: "gzipResponse",
  1611  										Options: RuleOptionsMap{
  1612  											"behavior": "ALWAYS",
  1613  										},
  1614  									},
  1615  								},
  1616  								Criteria: []RuleBehavior{
  1617  									{
  1618  										Locked: false,
  1619  										Name:   "contentType",
  1620  										Options: RuleOptionsMap{
  1621  											"matchOperator":      "IS_ONE_OF",
  1622  											"matchWildcard":      true,
  1623  											"matchCaseSensitive": false,
  1624  											"values":             []interface{}{"text/html*", "text/css*", "application/x-javascript*"},
  1625  										},
  1626  									},
  1627  								},
  1628  								Name: "Compress Text Content",
  1629  							},
  1630  						},
  1631  						Criteria: []RuleBehavior{},
  1632  						Name:     "default",
  1633  						Options:  RuleOptions{IsSecure: false},
  1634  						CustomOverride: &RuleCustomOverride{
  1635  							OverrideID: "cbo_12345",
  1636  							Name:       "mdc",
  1637  						},
  1638  						Variables: []RuleVariable{
  1639  							{
  1640  								Description: tools.StringPtr("This is a sample Property Manager variable."),
  1641  								Hidden:      false,
  1642  								Name:        "VAR_NAME",
  1643  								Sensitive:   false,
  1644  								Value:       tools.StringPtr("default value"),
  1645  							},
  1646  						},
  1647  					},
  1648  				},
  1649  			},
  1650  			withError: func(t *testing.T, err error) {
  1651  				want := ErrStructValidation
  1652  				assert.True(t, errors.Is(err, want), "want: %s; got: %s", want, err)
  1653  				assert.Contains(t, err.Error(), "ValidateMode")
  1654  			},
  1655  		},
  1656  		"empty name": {
  1657  			params: UpdateRulesRequest{
  1658  				PropertyID:      "propertyID",
  1659  				PropertyVersion: 2,
  1660  				ContractID:      "contract",
  1661  				GroupID:         "group",
  1662  				DryRun:          true,
  1663  				ValidateMode:    "fast",
  1664  				ValidateRules:   false,
  1665  				Rules: RulesUpdate{
  1666  					Rules: Rules{
  1667  						Behaviors: []RuleBehavior{
  1668  							{
  1669  								Name: "origin",
  1670  								Options: RuleOptionsMap{
  1671  									"httpPort":           float64(80),
  1672  									"enableTrueClientIp": false,
  1673  									"compress":           true,
  1674  									"cacheKeyHostname":   "ORIGIN_HOSTNAME",
  1675  									"forwardHostHeader":  "REQUEST_HOST_HEADER",
  1676  									"hostname":           "origin.test.com",
  1677  									"originType":         "CUSTOMER",
  1678  								},
  1679  							},
  1680  							{
  1681  								Name: "cpCode",
  1682  								Options: RuleOptionsMap{
  1683  									"value": map[string]interface{}{
  1684  										"id":   float64(12345),
  1685  										"name": "my CP code",
  1686  									},
  1687  								},
  1688  							},
  1689  						},
  1690  						Children: []Rules{
  1691  							{
  1692  								Behaviors: []RuleBehavior{
  1693  									{
  1694  										Name: "gzipResponse",
  1695  										Options: RuleOptionsMap{
  1696  											"behavior": "ALWAYS",
  1697  										},
  1698  									},
  1699  								},
  1700  								Criteria: []RuleBehavior{
  1701  									{
  1702  										Locked: false,
  1703  										Name:   "contentType",
  1704  										Options: RuleOptionsMap{
  1705  											"matchOperator":      "IS_ONE_OF",
  1706  											"matchWildcard":      true,
  1707  											"matchCaseSensitive": false,
  1708  											"values":             []interface{}{"text/html*", "text/css*", "application/x-javascript*"},
  1709  										},
  1710  									},
  1711  								},
  1712  								Name: "Compress Text Content",
  1713  							},
  1714  						},
  1715  						Criteria: []RuleBehavior{},
  1716  						Name:     "",
  1717  						Options:  RuleOptions{IsSecure: false},
  1718  						CustomOverride: &RuleCustomOverride{
  1719  							OverrideID: "cbo_12345",
  1720  							Name:       "mdc",
  1721  						},
  1722  						Variables: []RuleVariable{
  1723  							{
  1724  								Description: tools.StringPtr("This is a sample Property Manager variable."),
  1725  								Hidden:      false,
  1726  								Name:        "VAR_NAME",
  1727  								Sensitive:   false,
  1728  								Value:       tools.StringPtr("default value"),
  1729  							},
  1730  						},
  1731  					},
  1732  				},
  1733  			},
  1734  			withError: func(t *testing.T, err error) {
  1735  				want := ErrStructValidation
  1736  				assert.True(t, errors.Is(err, want), "want: %s; got: %s", want, err)
  1737  				assert.Contains(t, err.Error(), "Name")
  1738  			},
  1739  		},
  1740  		"empty name in custom override": {
  1741  			params: UpdateRulesRequest{
  1742  				PropertyID:      "propertyID",
  1743  				PropertyVersion: 2,
  1744  				ContractID:      "contract",
  1745  				GroupID:         "group",
  1746  				DryRun:          true,
  1747  				ValidateMode:    "test",
  1748  				ValidateRules:   false,
  1749  				Rules: RulesUpdate{
  1750  					Rules: Rules{
  1751  						Behaviors: []RuleBehavior{
  1752  							{
  1753  								Name: "origin",
  1754  								Options: RuleOptionsMap{
  1755  									"httpPort":           float64(80),
  1756  									"enableTrueClientIp": false,
  1757  									"compress":           true,
  1758  									"cacheKeyHostname":   "ORIGIN_HOSTNAME",
  1759  									"forwardHostHeader":  "REQUEST_HOST_HEADER",
  1760  									"hostname":           "origin.test.com",
  1761  									"originType":         "CUSTOMER",
  1762  								},
  1763  							},
  1764  							{
  1765  								Name: "cpCode",
  1766  								Options: RuleOptionsMap{
  1767  									"value": map[string]interface{}{
  1768  										"id":   float64(12345),
  1769  										"name": "my CP code",
  1770  									},
  1771  								},
  1772  							},
  1773  						},
  1774  						Children: []Rules{
  1775  							{
  1776  								Behaviors: []RuleBehavior{
  1777  									{
  1778  										Name: "gzipResponse",
  1779  										Options: RuleOptionsMap{
  1780  											"behavior": "ALWAYS",
  1781  										},
  1782  									},
  1783  								},
  1784  								Criteria: []RuleBehavior{
  1785  									{
  1786  										Locked: false,
  1787  										Name:   "contentType",
  1788  										Options: RuleOptionsMap{
  1789  											"matchOperator":      "IS_ONE_OF",
  1790  											"matchWildcard":      true,
  1791  											"matchCaseSensitive": false,
  1792  											"values":             []interface{}{"text/html*", "text/css*", "application/x-javascript*"},
  1793  										},
  1794  									},
  1795  								},
  1796  								Name: "Compress Text Content",
  1797  							},
  1798  						},
  1799  						Criteria: []RuleBehavior{},
  1800  						Name:     "default",
  1801  						Options:  RuleOptions{IsSecure: false},
  1802  						CustomOverride: &RuleCustomOverride{
  1803  							OverrideID: "cbo_12345",
  1804  							Name:       "",
  1805  						},
  1806  						Variables: []RuleVariable{
  1807  							{
  1808  								Description: tools.StringPtr("This is a sample Property Manager variable."),
  1809  								Hidden:      false,
  1810  								Name:        "VAR_NAME",
  1811  								Sensitive:   false,
  1812  								Value:       tools.StringPtr("default value"),
  1813  							},
  1814  						},
  1815  					},
  1816  				},
  1817  			},
  1818  			withError: func(t *testing.T, err error) {
  1819  				want := ErrStructValidation
  1820  				assert.True(t, errors.Is(err, want), "want: %s; got: %s", want, err)
  1821  				assert.Contains(t, err.Error(), "Name")
  1822  			},
  1823  		},
  1824  		"invalid overrideID in customOverride": {
  1825  			params: UpdateRulesRequest{
  1826  				PropertyID:      "propertyID",
  1827  				PropertyVersion: 2,
  1828  				ContractID:      "contract",
  1829  				GroupID:         "group",
  1830  				DryRun:          true,
  1831  				ValidateMode:    "test",
  1832  				ValidateRules:   false,
  1833  				Rules: RulesUpdate{
  1834  					Rules: Rules{
  1835  						Behaviors: []RuleBehavior{
  1836  							{
  1837  								Name: "origin",
  1838  								Options: RuleOptionsMap{
  1839  									"httpPort":           float64(80),
  1840  									"enableTrueClientIp": false,
  1841  									"compress":           true,
  1842  									"cacheKeyHostname":   "ORIGIN_HOSTNAME",
  1843  									"forwardHostHeader":  "REQUEST_HOST_HEADER",
  1844  									"hostname":           "origin.test.com",
  1845  									"originType":         "CUSTOMER",
  1846  								},
  1847  							},
  1848  							{
  1849  								Name: "cpCode",
  1850  								Options: RuleOptionsMap{
  1851  									"value": map[string]interface{}{
  1852  										"id":   float64(12345),
  1853  										"name": "my CP code",
  1854  									},
  1855  								},
  1856  							},
  1857  						},
  1858  						Children: []Rules{
  1859  							{
  1860  								Behaviors: []RuleBehavior{
  1861  									{
  1862  										Name: "gzipResponse",
  1863  										Options: RuleOptionsMap{
  1864  											"behavior": "ALWAYS",
  1865  										},
  1866  									},
  1867  								},
  1868  								Criteria: []RuleBehavior{
  1869  									{
  1870  										Locked: false,
  1871  										Name:   "contentType",
  1872  										Options: RuleOptionsMap{
  1873  											"matchOperator":      "IS_ONE_OF",
  1874  											"matchWildcard":      true,
  1875  											"matchCaseSensitive": false,
  1876  											"values":             []interface{}{"text/html*", "text/css*", "application/x-javascript*"},
  1877  										},
  1878  									},
  1879  								},
  1880  								Name: "Compress Text Content",
  1881  							},
  1882  						},
  1883  						Criteria: []RuleBehavior{},
  1884  						Name:     "default",
  1885  						Options:  RuleOptions{IsSecure: false},
  1886  						CustomOverride: &RuleCustomOverride{
  1887  							OverrideID: "",
  1888  							Name:       "mdc",
  1889  						},
  1890  						Variables: []RuleVariable{
  1891  							{
  1892  								Description: tools.StringPtr("This is a sample Property Manager variable."),
  1893  								Hidden:      false,
  1894  								Name:        "VAR_NAME",
  1895  								Sensitive:   false,
  1896  								Value:       tools.StringPtr("default value"),
  1897  							},
  1898  						},
  1899  					},
  1900  				},
  1901  			},
  1902  			withError: func(t *testing.T, err error) {
  1903  				want := ErrStructValidation
  1904  				assert.True(t, errors.Is(err, want), "want: %s; got: %s", want, err)
  1905  				assert.Contains(t, err.Error(), "OverrideID")
  1906  			},
  1907  		},
  1908  		"empty name in variable": {
  1909  			params: UpdateRulesRequest{
  1910  				PropertyID:      "propertyID",
  1911  				PropertyVersion: 2,
  1912  				ContractID:      "contract",
  1913  				GroupID:         "group",
  1914  				DryRun:          true,
  1915  				ValidateMode:    "test",
  1916  				ValidateRules:   false,
  1917  				Rules: RulesUpdate{
  1918  					Rules: Rules{
  1919  						Behaviors: []RuleBehavior{
  1920  							{
  1921  								Name: "origin",
  1922  								Options: RuleOptionsMap{
  1923  									"httpPort":           float64(80),
  1924  									"enableTrueClientIp": false,
  1925  									"compress":           true,
  1926  									"cacheKeyHostname":   "ORIGIN_HOSTNAME",
  1927  									"forwardHostHeader":  "REQUEST_HOST_HEADER",
  1928  									"hostname":           "origin.test.com",
  1929  									"originType":         "CUSTOMER",
  1930  								},
  1931  							},
  1932  							{
  1933  								Name: "cpCode",
  1934  								Options: RuleOptionsMap{
  1935  									"value": map[string]interface{}{
  1936  										"id":   float64(12345),
  1937  										"name": "my CP code",
  1938  									},
  1939  								},
  1940  							},
  1941  						},
  1942  						Children: []Rules{
  1943  							{
  1944  								Behaviors: []RuleBehavior{
  1945  									{
  1946  										Name: "gzipResponse",
  1947  										Options: RuleOptionsMap{
  1948  											"behavior": "ALWAYS",
  1949  										},
  1950  									},
  1951  								},
  1952  								Criteria: []RuleBehavior{
  1953  									{
  1954  										Locked: false,
  1955  										Name:   "contentType",
  1956  										Options: RuleOptionsMap{
  1957  											"matchOperator":      "IS_ONE_OF",
  1958  											"matchWildcard":      true,
  1959  											"matchCaseSensitive": false,
  1960  											"values":             []interface{}{"text/html*", "text/css*", "application/x-javascript*"},
  1961  										},
  1962  									},
  1963  								},
  1964  								Name: "Compress Text Content",
  1965  							},
  1966  						},
  1967  						Criteria: []RuleBehavior{},
  1968  						Name:     "default",
  1969  						Options:  RuleOptions{IsSecure: false},
  1970  						CustomOverride: &RuleCustomOverride{
  1971  							OverrideID: "cbo_12345",
  1972  							Name:       "mdc",
  1973  						},
  1974  						Variables: []RuleVariable{
  1975  							{
  1976  								Description: tools.StringPtr("This is a sample Property Manager variable."),
  1977  								Hidden:      false,
  1978  								Name:        "",
  1979  								Sensitive:   false,
  1980  								Value:       tools.StringPtr("default value"),
  1981  							},
  1982  						},
  1983  					},
  1984  				},
  1985  			},
  1986  			withError: func(t *testing.T, err error) {
  1987  				want := ErrStructValidation
  1988  				assert.True(t, errors.Is(err, want), "want: %s; got: %s", want, err)
  1989  				assert.Contains(t, err.Error(), "Name")
  1990  			},
  1991  		},
  1992  	}
  1993  
  1994  	for name, test := range tests {
  1995  		t.Run(name, func(t *testing.T) {
  1996  			mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  1997  				assert.Equal(t, test.expectedPath, r.URL.String())
  1998  				assert.Equal(t, http.MethodPut, r.Method)
  1999  				if test.requestBody != "" {
  2000  					buf := new(bytes.Buffer)
  2001  					_, err := buf.ReadFrom(r.Body)
  2002  					assert.NoError(t, err)
  2003  					req := buf.String()
  2004  					assert.Equal(t, test.requestBody, req)
  2005  				}
  2006  				w.WriteHeader(test.responseStatus)
  2007  				_, err := w.Write([]byte(test.responseBody))
  2008  				assert.NoError(t, err)
  2009  			}))
  2010  			client := mockAPIClient(t, mockServer)
  2011  			result, err := client.UpdateRuleTree(context.Background(), test.params)
  2012  			if test.withError != nil {
  2013  				test.withError(t, err)
  2014  				return
  2015  			}
  2016  			require.NoError(t, err)
  2017  			assert.Equal(t, test.expectedResponse, result)
  2018  		})
  2019  	}
  2020  }