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

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