github.com/akamai/AkamaiOPEN-edgegrid-golang/v8@v8.1.0/pkg/papi/include_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/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/tools"
    12  
    13  	"github.com/stretchr/testify/assert"
    14  	"github.com/stretchr/testify/require"
    15  )
    16  
    17  func TestGetIncludeRuleTree(t *testing.T) {
    18  	tests := map[string]struct {
    19  		params           GetIncludeRuleTreeRequest
    20  		responseStatus   int
    21  		responseBody     string
    22  		expectedPath     string
    23  		expectedResponse *GetIncludeRuleTreeResponse
    24  		withError        error
    25  	}{
    26  		"200 OK - get include": {
    27  			params: GetIncludeRuleTreeRequest{
    28  				IncludeID:      "inc_123456",
    29  				IncludeVersion: 2,
    30  				ContractID:     "test_contract",
    31  				GroupID:        "test_group",
    32  				ValidateMode:   "fast",
    33  				ValidateRules:  false,
    34  			},
    35  			responseStatus: http.StatusOK,
    36  			responseBody: `
    37  {
    38      "accountId": "test_account",
    39      "contractId": "test_contract",
    40      "groupId": "test_group",
    41      "includeId": "inc_123456",
    42      "includeVersion": 2,
    43  	"includeName": "test_include",
    44  	"includeType": "MICROSERVICES",
    45      "etag": "etag",
    46      "ruleFormat": "v2020-11-02",
    47      "rules": {
    48          "name": "default",
    49          "criteria": [],
    50          "children": [
    51              {
    52                  "name": "Compress Text Content",
    53                  "criteria": [
    54                      {
    55                          "name": "contentType",
    56                          "options": {
    57                              "matchOperator": "IS_ONE_OF",
    58                              "matchWildcard": true,
    59                              "matchCaseSensitive": false,
    60                              "values": [
    61                                  "text/html*",
    62                                  "text/css*",
    63                                  "application/x-javascript*"
    64                              ]
    65                          }
    66                      }
    67                  ],
    68                  "behaviors": [
    69                      {
    70                          "name": "gzipResponse",
    71                          "options": { "behavior": "ALWAYS" }
    72                      }
    73                  ]
    74              }
    75          ],
    76          "options": {
    77              "is_secure": false
    78          },
    79          "behaviors": [
    80              {
    81                  "name": "origin",
    82                  "options": {
    83                      "httpPort": 80,
    84                      "enableTrueClientIp": false,
    85                      "compress": true,
    86                      "cacheKeyHostname": "ORIGIN_HOSTNAME",
    87                      "forwardHostHeader": "REQUEST_HOST_HEADER",
    88                      "hostname": "origin.test.com",
    89                      "originType": "CUSTOMER"
    90                  }
    91              },
    92              {
    93                  "name": "cpCode",
    94                  "options": {
    95                      "value": {
    96                          "id": 12345,
    97                          "name": "my CP code"
    98                      }
    99                  }
   100              }
   101          ],
   102   		"customOverride": {
   103          	"overrideId": "cbo_12345",
   104          	"name": "mdc"
   105      	},
   106  		"variables": [
   107              {
   108                  "name": "VAR_NAME",
   109                  "value": "default value",
   110                  "description": "This is a sample Property Manager variable.",
   111                  "hidden": false,
   112                  "sensitive": false
   113              }
   114          ]
   115      }
   116  }`,
   117  			expectedPath: "/papi/v1/includes/inc_123456/versions/2/rules?contractId=test_contract&groupId=test_group&validateMode=fast&validateRules=false",
   118  			expectedResponse: &GetIncludeRuleTreeResponse{
   119  				Response: Response{
   120  					AccountID:  "test_account",
   121  					ContractID: "test_contract",
   122  					GroupID:    "test_group",
   123  				},
   124  				IncludeID:      "inc_123456",
   125  				IncludeVersion: 2,
   126  				IncludeName:    "test_include",
   127  				IncludeType:    IncludeTypeMicroServices,
   128  				Etag:           "etag",
   129  				RuleFormat:     "v2020-11-02",
   130  				Rules: Rules{
   131  					Behaviors: []RuleBehavior{
   132  						{
   133  							Name: "origin",
   134  							Options: RuleOptionsMap{
   135  								"httpPort":           float64(80),
   136  								"enableTrueClientIp": false,
   137  								"compress":           true,
   138  								"cacheKeyHostname":   "ORIGIN_HOSTNAME",
   139  								"forwardHostHeader":  "REQUEST_HOST_HEADER",
   140  								"hostname":           "origin.test.com",
   141  								"originType":         "CUSTOMER",
   142  							},
   143  						},
   144  						{
   145  							Name: "cpCode",
   146  							Options: RuleOptionsMap{
   147  								"value": map[string]interface{}{
   148  									"id":   float64(12345),
   149  									"name": "my CP code",
   150  								},
   151  							},
   152  						},
   153  					},
   154  					Children: []Rules{
   155  						{
   156  							Behaviors: []RuleBehavior{
   157  								{
   158  									Name: "gzipResponse",
   159  									Options: RuleOptionsMap{
   160  										"behavior": "ALWAYS",
   161  									},
   162  								},
   163  							},
   164  							Criteria: []RuleBehavior{
   165  								{
   166  									Locked: false,
   167  									Name:   "contentType",
   168  									Options: RuleOptionsMap{
   169  										"matchOperator":      "IS_ONE_OF",
   170  										"matchWildcard":      true,
   171  										"matchCaseSensitive": false,
   172  										"values":             []interface{}{"text/html*", "text/css*", "application/x-javascript*"},
   173  									},
   174  								},
   175  							},
   176  							Name: "Compress Text Content",
   177  						},
   178  					},
   179  					Criteria: []RuleBehavior{},
   180  					Name:     "default",
   181  					Options:  RuleOptions{IsSecure: false},
   182  					CustomOverride: &RuleCustomOverride{
   183  						OverrideID: "cbo_12345",
   184  						Name:       "mdc",
   185  					},
   186  					Variables: []RuleVariable{
   187  						{
   188  							Description: tools.StringPtr("This is a sample Property Manager variable."),
   189  							Hidden:      false,
   190  							Name:        "VAR_NAME",
   191  							Sensitive:   false,
   192  							Value:       tools.StringPtr("default value"),
   193  						},
   194  					},
   195  				},
   196  			},
   197  		},
   198  		"200 OK - get include with ruleFormat": {
   199  			params: GetIncludeRuleTreeRequest{
   200  				IncludeID:      "inc_123456",
   201  				IncludeVersion: 2,
   202  				ContractID:     "test_contract",
   203  				GroupID:        "test_group",
   204  				RuleFormat:     "v2020-11-02",
   205  				ValidateMode:   "fast",
   206  				ValidateRules:  false,
   207  			},
   208  			responseStatus: http.StatusOK,
   209  			responseBody: `
   210  {
   211      "accountId": "test_account",
   212      "contractId": "test_contract",
   213      "groupId": "test_group",
   214      "includeId": "inc_123456",
   215      "includeVersion": 2,
   216  	"includeName": "test_include",
   217  	"includeType": "MICROSERVICES",
   218      "etag": "etag",
   219      "ruleFormat": "v2020-11-02",
   220      "rules": {
   221          "name": "default",
   222          "criteria": [],
   223          "children": [
   224              {
   225                  "name": "Compress Text Content",
   226                  "criteria": [
   227                      {
   228                          "name": "contentType",
   229                          "options": {
   230                              "matchOperator": "IS_ONE_OF",
   231                              "matchWildcard": true,
   232                              "matchCaseSensitive": false,
   233                              "values": [
   234                                  "text/html*",
   235                                  "text/css*",
   236                                  "application/x-javascript*"
   237                              ]
   238                          }
   239                      }
   240                  ],
   241                  "behaviors": [
   242                      {
   243                          "name": "gzipResponse",
   244                          "options": { "behavior": "ALWAYS" }
   245                      }
   246                  ]
   247              }
   248          ],
   249          "options": {
   250              "is_secure": false
   251          },
   252          "behaviors": [
   253              {
   254                  "name": "origin",
   255                  "options": {
   256                      "httpPort": 80,
   257                      "enableTrueClientIp": false,
   258                      "compress": true,
   259                      "cacheKeyHostname": "ORIGIN_HOSTNAME",
   260                      "forwardHostHeader": "REQUEST_HOST_HEADER",
   261                      "hostname": "origin.test.com",
   262                      "originType": "CUSTOMER"
   263                  }
   264              },
   265              {
   266                  "name": "cpCode",
   267                  "options": {
   268                      "value": {
   269                          "id": 12345,
   270                          "name": "my CP code"
   271                      }
   272                  }
   273              }
   274          ],
   275   		"customOverride": {
   276          	"overrideId": "cbo_12345",
   277          	"name": "mdc"
   278      	},
   279  		"variables": [
   280              {
   281                  "name": "VAR_NAME",
   282                  "value": "default value",
   283                  "description": "This is a sample Property Manager variable.",
   284                  "hidden": false,
   285                  "sensitive": false
   286              }
   287          ]
   288      }
   289  }`,
   290  			expectedPath: "/papi/v1/includes/inc_123456/versions/2/rules?contractId=test_contract&groupId=test_group&validateMode=fast&validateRules=false",
   291  			expectedResponse: &GetIncludeRuleTreeResponse{
   292  				Response: Response{
   293  					AccountID:  "test_account",
   294  					ContractID: "test_contract",
   295  					GroupID:    "test_group",
   296  				},
   297  				IncludeID:      "inc_123456",
   298  				IncludeVersion: 2,
   299  				IncludeName:    "test_include",
   300  				IncludeType:    IncludeTypeMicroServices,
   301  				Etag:           "etag",
   302  				RuleFormat:     "v2020-11-02",
   303  				Rules: Rules{
   304  					Behaviors: []RuleBehavior{
   305  						{
   306  							Name: "origin",
   307  							Options: RuleOptionsMap{
   308  								"httpPort":           float64(80),
   309  								"enableTrueClientIp": false,
   310  								"compress":           true,
   311  								"cacheKeyHostname":   "ORIGIN_HOSTNAME",
   312  								"forwardHostHeader":  "REQUEST_HOST_HEADER",
   313  								"hostname":           "origin.test.com",
   314  								"originType":         "CUSTOMER",
   315  							},
   316  						},
   317  						{
   318  							Name: "cpCode",
   319  							Options: RuleOptionsMap{
   320  								"value": map[string]interface{}{
   321  									"id":   float64(12345),
   322  									"name": "my CP code",
   323  								},
   324  							},
   325  						},
   326  					},
   327  					Children: []Rules{
   328  						{
   329  							Behaviors: []RuleBehavior{
   330  								{
   331  									Name: "gzipResponse",
   332  									Options: RuleOptionsMap{
   333  										"behavior": "ALWAYS",
   334  									},
   335  								},
   336  							},
   337  							Criteria: []RuleBehavior{
   338  								{
   339  									Locked: false,
   340  									Name:   "contentType",
   341  									Options: RuleOptionsMap{
   342  										"matchOperator":      "IS_ONE_OF",
   343  										"matchWildcard":      true,
   344  										"matchCaseSensitive": false,
   345  										"values":             []interface{}{"text/html*", "text/css*", "application/x-javascript*"},
   346  									},
   347  								},
   348  							},
   349  							Name: "Compress Text Content",
   350  						},
   351  					},
   352  					Criteria: []RuleBehavior{},
   353  					Name:     "default",
   354  					Options:  RuleOptions{IsSecure: false},
   355  					CustomOverride: &RuleCustomOverride{
   356  						OverrideID: "cbo_12345",
   357  						Name:       "mdc",
   358  					},
   359  					Variables: []RuleVariable{
   360  						{
   361  							Description: tools.StringPtr("This is a sample Property Manager variable."),
   362  							Hidden:      false,
   363  							Name:        "VAR_NAME",
   364  							Sensitive:   false,
   365  							Value:       tools.StringPtr("default value"),
   366  						},
   367  					},
   368  				},
   369  			},
   370  		},
   371  		"500 Internal Server Error": {
   372  			params: GetIncludeRuleTreeRequest{
   373  				IncludeID:      "inc_123456",
   374  				IncludeVersion: 2,
   375  				ContractID:     "test_contract",
   376  				GroupID:        "test_group",
   377  				ValidateMode:   "fast",
   378  				ValidateRules:  false,
   379  			},
   380  			responseStatus: http.StatusInternalServerError,
   381  			responseBody: `
   382  {
   383  	"type": "internal_error",
   384      "title": "Internal Server Error",
   385      "detail": "Error fetching rule tree",
   386      "status": 500
   387  }`,
   388  			expectedPath: "/papi/v1/includes/inc_123456/versions/2/rules?contractId=test_contract&groupId=test_group&validateMode=fast&validateRules=false",
   389  			withError: &Error{
   390  				Type:       "internal_error",
   391  				Title:      "Internal Server Error",
   392  				Detail:     "Error fetching rule tree",
   393  				StatusCode: http.StatusInternalServerError,
   394  			},
   395  		},
   396  		"validation error - missing includeId": {
   397  			params: GetIncludeRuleTreeRequest{
   398  				IncludeVersion: 2,
   399  				ContractID:     "test_contract",
   400  				GroupID:        "test_group",
   401  				ValidateMode:   "fast",
   402  				ValidateRules:  false,
   403  			},
   404  			withError: ErrStructValidation,
   405  		},
   406  		"validation error - missing includeVersion": {
   407  			params: GetIncludeRuleTreeRequest{
   408  				IncludeID:     "inc_123456",
   409  				ContractID:    "test_contract",
   410  				GroupID:       "test_group",
   411  				ValidateMode:  "fast",
   412  				ValidateRules: false,
   413  			},
   414  			withError: ErrStructValidation,
   415  		},
   416  		"validation error - missing contractId": {
   417  			params: GetIncludeRuleTreeRequest{
   418  				IncludeID:      "inc_123456",
   419  				IncludeVersion: 2,
   420  				GroupID:        "test_group",
   421  				ValidateMode:   "fast",
   422  				ValidateRules:  false,
   423  			},
   424  			withError: ErrStructValidation,
   425  		},
   426  		"validation error - missing groupId": {
   427  			params: GetIncludeRuleTreeRequest{
   428  				IncludeID:      "inc_123456",
   429  				IncludeVersion: 2,
   430  				ContractID:     "test_contract",
   431  				ValidateMode:   "fast",
   432  				ValidateRules:  false,
   433  			},
   434  			withError: ErrStructValidation,
   435  		},
   436  		"validation error - invalid validation mode": {
   437  			params: GetIncludeRuleTreeRequest{
   438  				IncludeID:      "inc_123456",
   439  				IncludeVersion: 2,
   440  				ContractID:     "test_contract",
   441  				GroupID:        "test_group",
   442  				ValidateMode:   "test",
   443  				ValidateRules:  false,
   444  			},
   445  			withError: ErrStructValidation,
   446  		},
   447  		"validation error - invalid ruleFormat": {
   448  			params: GetIncludeRuleTreeRequest{
   449  				IncludeID:      "inc_123456",
   450  				IncludeVersion: 2,
   451  				ContractID:     "test_contract",
   452  				GroupID:        "test_group",
   453  				RuleFormat:     "invalid",
   454  				ValidateMode:   "fast",
   455  				ValidateRules:  false,
   456  			},
   457  			withError: ErrStructValidation,
   458  		},
   459  	}
   460  
   461  	for name, test := range tests {
   462  		t.Run(name, func(t *testing.T) {
   463  			mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   464  				assert.Equal(t, test.expectedPath, r.URL.String())
   465  				assert.Equal(t, http.MethodGet, r.Method)
   466  				w.WriteHeader(test.responseStatus)
   467  				_, err := w.Write([]byte(test.responseBody))
   468  				if test.params.RuleFormat != "" {
   469  					assert.Equal(t, r.Header.Get("Accept"), fmt.Sprintf("application/vnd.akamai.papirules.%s+json", test.params.RuleFormat))
   470  				}
   471  				assert.NoError(t, err)
   472  			}))
   473  			client := mockAPIClient(t, mockServer)
   474  			result, err := client.GetIncludeRuleTree(context.Background(), test.params)
   475  			if test.withError != nil {
   476  				assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err)
   477  				return
   478  			}
   479  			require.NoError(t, err)
   480  			assert.Equal(t, test.expectedResponse, result)
   481  		})
   482  	}
   483  }
   484  
   485  func TestUpdateIncludeRuleTree(t *testing.T) {
   486  	tests := map[string]struct {
   487  		params           UpdateIncludeRuleTreeRequest
   488  		responseStatus   int
   489  		responseBody     string
   490  		responseHeaders  map[string]string
   491  		expectedPath     string
   492  		expectedResponse *UpdateIncludeRuleTreeResponse
   493  		withError        error
   494  	}{
   495  		"200 OK - update rule tree": {
   496  			params: UpdateIncludeRuleTreeRequest{
   497  				IncludeID:      "inc_123456",
   498  				IncludeVersion: 2,
   499  				ContractID:     "test_contract",
   500  				GroupID:        "test_group",
   501  				DryRun:         true,
   502  				ValidateMode:   "fast",
   503  				ValidateRules:  false,
   504  				Rules: RulesUpdate{
   505  					Comments: "version comment",
   506  					Rules: Rules{
   507  						Comments: "default comment",
   508  						Behaviors: []RuleBehavior{
   509  							{
   510  								Name: "origin",
   511  								Options: RuleOptionsMap{
   512  									"httpPort":           float64(80),
   513  									"enableTrueClientIp": false,
   514  									"compress":           true,
   515  									"cacheKeyHostname":   "ORIGIN_HOSTNAME",
   516  									"forwardHostHeader":  "REQUEST_HOST_HEADER",
   517  									"hostname":           "origin.test.com",
   518  									"originType":         "CUSTOMER",
   519  								},
   520  							},
   521  							{
   522  								Name: "cpCode",
   523  								Options: RuleOptionsMap{
   524  									"value": map[string]interface{}{
   525  										"id":   float64(12345),
   526  										"name": "my CP code",
   527  									},
   528  								},
   529  							},
   530  						},
   531  						Children: []Rules{
   532  							{
   533  								Behaviors: []RuleBehavior{
   534  									{
   535  										Name: "gzipResponse",
   536  										Options: RuleOptionsMap{
   537  											"behavior": "ALWAYS",
   538  										},
   539  									},
   540  								},
   541  								Criteria: []RuleBehavior{
   542  									{
   543  										Locked: false,
   544  										Name:   "contentType",
   545  										Options: RuleOptionsMap{
   546  											"matchOperator":      "IS_ONE_OF",
   547  											"matchWildcard":      true,
   548  											"matchCaseSensitive": false,
   549  											"values":             []interface{}{"text/html*", "text/css*", "application/x-javascript*"},
   550  										},
   551  									},
   552  								},
   553  								Name: "Compress Text Content",
   554  							},
   555  						},
   556  						Criteria: []RuleBehavior{},
   557  						Name:     "default",
   558  						Options:  RuleOptions{IsSecure: false},
   559  						CustomOverride: &RuleCustomOverride{
   560  							OverrideID: "cbo_12345",
   561  							Name:       "mdc",
   562  						},
   563  						Variables: []RuleVariable{
   564  							{
   565  								Description: tools.StringPtr("This is a sample Property Manager variable."),
   566  								Hidden:      false,
   567  								Name:        "VAR_NAME",
   568  								Sensitive:   false,
   569  								Value:       tools.StringPtr("default value"),
   570  							},
   571  						},
   572  					},
   573  				},
   574  			},
   575  			responseStatus: http.StatusOK,
   576  			responseHeaders: map[string]string{
   577  				"x-limit-elements-per-property-limit":            "3000",
   578  				"x-limit-elements-per-property-remaining":        "3000",
   579  				"x-limit-max-nested-rules-per-include-limit":     "4",
   580  				"x-limit-max-nested-rules-per-include-remaining": "4",
   581  			},
   582  			responseBody: `
   583  {
   584      "accountId": "test_account",
   585      "contractId": "test_contract",
   586      "groupId": "test_group",
   587      "includeId": "inc_123456",
   588      "includeVersion": 2,
   589     	"includeName": "test_include",
   590      "includeType": "MICROSERVICES",
   591      "etag": "etag",
   592      "ruleFormat": "v2020-11-02",
   593  	"comments": "version comment",
   594      "rules": {
   595          "name": "default",
   596          "comments": "default comment",
   597          "criteria": [],
   598          "children": [
   599              {
   600                  "name": "Compress Text Content",
   601                  "criteria": [
   602                      {
   603                          "name": "contentType",
   604                          "options": {
   605                              "matchOperator": "IS_ONE_OF",
   606                              "matchWildcard": true,
   607                              "matchCaseSensitive": false,
   608                              "values": [
   609                                  "text/html*",
   610                                  "text/css*",
   611                                  "application/x-javascript*"
   612                              ]
   613                          }
   614                      }
   615                  ],
   616                  "behaviors": [
   617                      {
   618                          "name": "gzipResponse",
   619                          "options": { "behavior": "ALWAYS" }
   620                      }
   621                  ]
   622              }
   623          ],
   624          "options": {
   625              "is_secure": false
   626          },
   627          "behaviors": [
   628              {
   629                  "name": "origin",
   630                  "options": {
   631                      "httpPort": 80,
   632                      "enableTrueClientIp": false,
   633                      "compress": true,
   634                      "cacheKeyHostname": "ORIGIN_HOSTNAME",
   635                      "forwardHostHeader": "REQUEST_HOST_HEADER",
   636                      "hostname": "origin.test.com",
   637                      "originType": "CUSTOMER"
   638                  }
   639              },
   640              {
   641                  "name": "cpCode",
   642                  "options": {
   643                      "value": {
   644                          "id": 12345,
   645                          "name": "my CP code"
   646                      }
   647                  }
   648              },
   649   			{
   650                  "name": "matchAdvanced",
   651                  "options": {
   652                      "value": {
   653                          "id": 12345,
   654                          "name": "my advanced match"
   655                      }
   656                  },
   657  				"locked": true,
   658  				"uuid": "fd6a63bc-120a-4891-a5f2-c479765d5553",
   659  				"templateUuid": "bedbac99-4ce1-43a3-96cc-b84c8cd30176"
   660              }
   661          ],
   662   		"customOverride": {
   663          	"overrideId": "cbo_12345",
   664          	"name": "mdc"
   665      	},
   666  		"templateUuid": "bedbac99-4ce1-43a3-96cc-b84c8cd30176",
   667  		"templateLink": "/platformtoolkit/service/ruletemplate/30582260/1?accountId=1-1TJZFB&gid=61726&ck=16.3.1.1",
   668  		"variables": [
   669              {
   670                  "name": "VAR_NAME",
   671                  "value": "default value",
   672                  "description": "This is a sample Property Manager variable.",
   673                  "hidden": false,
   674                  "sensitive": false
   675              }
   676          ]
   677      },
   678  	"errors": [
   679          {
   680              "type": "https://problems.example.net/papi/v0/validation/attribute_required",
   681              "errorLocation": "#/rules/behaviors/0/options/reason",
   682              "detail": "The Reason ID option on the Control Access behavior is required."
   683          }
   684      ]
   685  }`,
   686  			expectedPath: "/papi/v1/includes/inc_123456/versions/2/rules?contractId=test_contract&dryRun=true&groupId=test_group&validateMode=fast&validateRules=false",
   687  			expectedResponse: &UpdateIncludeRuleTreeResponse{
   688  				Response: Response{
   689  					AccountID:  "test_account",
   690  					ContractID: "test_contract",
   691  					GroupID:    "test_group",
   692  					Errors: []*Error{
   693  						{
   694  							Type:          "https://problems.example.net/papi/v0/validation/attribute_required",
   695  							ErrorLocation: "#/rules/behaviors/0/options/reason",
   696  							Detail:        "The Reason ID option on the Control Access behavior is required.",
   697  						},
   698  					},
   699  				},
   700  				ResponseHeaders: UpdateIncludeResponseHeaders{
   701  					ElementsPerPropertyRemaining:      "3000",
   702  					ElementsPerPropertyTotal:          "3000",
   703  					MaxNestedRulesPerIncludeRemaining: "4",
   704  					MaxNestedRulesPerIncludeTotal:     "4",
   705  				},
   706  				IncludeID:      "inc_123456",
   707  				IncludeVersion: 2,
   708  				IncludeName:    "test_include",
   709  				IncludeType:    IncludeTypeMicroServices,
   710  				Etag:           "etag",
   711  				RuleFormat:     "v2020-11-02",
   712  				Comments:       "version comment",
   713  				Rules: Rules{
   714  					Comments: "default comment",
   715  					Behaviors: []RuleBehavior{
   716  						{
   717  							Name: "origin",
   718  							Options: RuleOptionsMap{
   719  								"httpPort":           float64(80),
   720  								"enableTrueClientIp": false,
   721  								"compress":           true,
   722  								"cacheKeyHostname":   "ORIGIN_HOSTNAME",
   723  								"forwardHostHeader":  "REQUEST_HOST_HEADER",
   724  								"hostname":           "origin.test.com",
   725  								"originType":         "CUSTOMER",
   726  							},
   727  						},
   728  						{
   729  							Name: "cpCode",
   730  							Options: RuleOptionsMap{
   731  								"value": map[string]interface{}{
   732  									"id":   float64(12345),
   733  									"name": "my CP code",
   734  								},
   735  							},
   736  						},
   737  						{
   738  							Name: "matchAdvanced",
   739  							Options: RuleOptionsMap{
   740  								"value": map[string]interface{}{
   741  									"id":   float64(12345),
   742  									"name": "my advanced match",
   743  								},
   744  							},
   745  							Locked:       true,
   746  							UUID:         "fd6a63bc-120a-4891-a5f2-c479765d5553",
   747  							TemplateUuid: "bedbac99-4ce1-43a3-96cc-b84c8cd30176",
   748  						},
   749  					},
   750  					Children: []Rules{
   751  						{
   752  							Behaviors: []RuleBehavior{
   753  								{
   754  									Name: "gzipResponse",
   755  									Options: RuleOptionsMap{
   756  										"behavior": "ALWAYS",
   757  									},
   758  								},
   759  							},
   760  							Criteria: []RuleBehavior{
   761  								{
   762  									Locked: false,
   763  									Name:   "contentType",
   764  									Options: RuleOptionsMap{
   765  										"matchOperator":      "IS_ONE_OF",
   766  										"matchWildcard":      true,
   767  										"matchCaseSensitive": false,
   768  										"values":             []interface{}{"text/html*", "text/css*", "application/x-javascript*"},
   769  									},
   770  								},
   771  							},
   772  							Name: "Compress Text Content",
   773  						},
   774  					},
   775  					Criteria: []RuleBehavior{},
   776  					Name:     "default",
   777  					Options:  RuleOptions{IsSecure: false},
   778  					CustomOverride: &RuleCustomOverride{
   779  						OverrideID: "cbo_12345",
   780  						Name:       "mdc",
   781  					},
   782  					TemplateUuid: "bedbac99-4ce1-43a3-96cc-b84c8cd30176",
   783  					TemplateLink: "/platformtoolkit/service/ruletemplate/30582260/1?accountId=1-1TJZFB&gid=61726&ck=16.3.1.1",
   784  					Variables: []RuleVariable{
   785  						{
   786  							Description: tools.StringPtr("This is a sample Property Manager variable."),
   787  							Hidden:      false,
   788  							Name:        "VAR_NAME",
   789  							Sensitive:   false,
   790  							Value:       tools.StringPtr("default value"),
   791  						},
   792  					},
   793  				},
   794  			},
   795  		},
   796  		"500 Internal Server Error": {
   797  			params: UpdateIncludeRuleTreeRequest{
   798  				IncludeID:      "inc_123456",
   799  				IncludeVersion: 2,
   800  				ContractID:     "test_contract",
   801  				GroupID:        "test_group",
   802  				DryRun:         true,
   803  				ValidateMode:   "fast",
   804  				ValidateRules:  false,
   805  				Rules: RulesUpdate{
   806  					Comments: "version comment",
   807  					Rules: Rules{
   808  						Comments: "default comment",
   809  						Behaviors: []RuleBehavior{
   810  							{
   811  								Name: "origin",
   812  								Options: RuleOptionsMap{
   813  									"httpPort":           float64(80),
   814  									"enableTrueClientIp": false,
   815  									"compress":           true,
   816  									"cacheKeyHostname":   "ORIGIN_HOSTNAME",
   817  									"forwardHostHeader":  "REQUEST_HOST_HEADER",
   818  									"hostname":           "origin.test.com",
   819  									"originType":         "CUSTOMER",
   820  								},
   821  							},
   822  							{
   823  								Name: "cpCode",
   824  								Options: RuleOptionsMap{
   825  									"value": map[string]interface{}{
   826  										"id":   float64(12345),
   827  										"name": "my CP code",
   828  									},
   829  								},
   830  							},
   831  						},
   832  						Children: []Rules{
   833  							{
   834  								Behaviors: []RuleBehavior{
   835  									{
   836  										Name: "gzipResponse",
   837  										Options: RuleOptionsMap{
   838  											"behavior": "ALWAYS",
   839  										},
   840  									},
   841  								},
   842  								Criteria: []RuleBehavior{
   843  									{
   844  										Locked: false,
   845  										Name:   "contentType",
   846  										Options: RuleOptionsMap{
   847  											"matchOperator":      "IS_ONE_OF",
   848  											"matchWildcard":      true,
   849  											"matchCaseSensitive": false,
   850  											"values":             []interface{}{"text/html*", "text/css*", "application/x-javascript*"},
   851  										},
   852  									},
   853  								},
   854  								Name: "Compress Text Content",
   855  							},
   856  						},
   857  						Criteria: []RuleBehavior{},
   858  						Name:     "default",
   859  						Options:  RuleOptions{IsSecure: false},
   860  						CustomOverride: &RuleCustomOverride{
   861  							OverrideID: "cbo_12345",
   862  							Name:       "mdc",
   863  						},
   864  						Variables: []RuleVariable{
   865  							{
   866  								Description: tools.StringPtr("This is a sample Property Manager variable."),
   867  								Hidden:      false,
   868  								Name:        "VAR_NAME",
   869  								Sensitive:   false,
   870  								Value:       tools.StringPtr("default value"),
   871  							},
   872  						},
   873  					},
   874  				},
   875  			},
   876  			responseStatus: http.StatusInternalServerError,
   877  			responseBody: `
   878  {
   879  	"type": "internal_error",
   880      "title": "Internal Server Error",
   881      "detail": "Error updating rule tree",
   882      "status": 500
   883  }`,
   884  			expectedPath: "/papi/v1/includes/inc_123456/versions/2/rules?contractId=test_contract&dryRun=true&groupId=test_group&validateMode=fast&validateRules=false",
   885  			withError: &Error{
   886  				Type:       "internal_error",
   887  				Title:      "Internal Server Error",
   888  				Detail:     "Error updating rule tree",
   889  				StatusCode: http.StatusInternalServerError,
   890  			},
   891  		},
   892  		"validation error - missing includeId": {
   893  			params: UpdateIncludeRuleTreeRequest{
   894  				IncludeVersion: 2,
   895  				ContractID:     "test_contract",
   896  				GroupID:        "test_group",
   897  				DryRun:         true,
   898  				ValidateMode:   "fast",
   899  				ValidateRules:  false,
   900  				Rules: RulesUpdate{
   901  					Comments: "version comment",
   902  					Rules: Rules{
   903  						Comments: "default comment",
   904  						Behaviors: []RuleBehavior{
   905  							{
   906  								Name: "origin",
   907  								Options: RuleOptionsMap{
   908  									"httpPort":           float64(80),
   909  									"enableTrueClientIp": false,
   910  									"compress":           true,
   911  									"cacheKeyHostname":   "ORIGIN_HOSTNAME",
   912  									"forwardHostHeader":  "REQUEST_HOST_HEADER",
   913  									"hostname":           "origin.test.com",
   914  									"originType":         "CUSTOMER",
   915  								},
   916  							},
   917  							{
   918  								Name: "cpCode",
   919  								Options: RuleOptionsMap{
   920  									"value": map[string]interface{}{
   921  										"id":   float64(12345),
   922  										"name": "my CP code",
   923  									},
   924  								},
   925  							},
   926  						},
   927  						Children: []Rules{
   928  							{
   929  								Behaviors: []RuleBehavior{
   930  									{
   931  										Name: "gzipResponse",
   932  										Options: RuleOptionsMap{
   933  											"behavior": "ALWAYS",
   934  										},
   935  									},
   936  								},
   937  								Criteria: []RuleBehavior{
   938  									{
   939  										Locked: false,
   940  										Name:   "contentType",
   941  										Options: RuleOptionsMap{
   942  											"matchOperator":      "IS_ONE_OF",
   943  											"matchWildcard":      true,
   944  											"matchCaseSensitive": false,
   945  											"values":             []interface{}{"text/html*", "text/css*", "application/x-javascript*"},
   946  										},
   947  									},
   948  								},
   949  								Name: "Compress Text Content",
   950  							},
   951  						},
   952  						Criteria: []RuleBehavior{},
   953  						Name:     "default",
   954  						Options:  RuleOptions{IsSecure: false},
   955  						CustomOverride: &RuleCustomOverride{
   956  							OverrideID: "cbo_12345",
   957  							Name:       "mdc",
   958  						},
   959  						Variables: []RuleVariable{
   960  							{
   961  								Description: tools.StringPtr("This is a sample Property Manager variable."),
   962  								Hidden:      false,
   963  								Name:        "VAR_NAME",
   964  								Sensitive:   false,
   965  								Value:       tools.StringPtr("default value"),
   966  							},
   967  						},
   968  					},
   969  				},
   970  			},
   971  			withError: ErrStructValidation,
   972  		},
   973  		"validation error - missing includeVersion": {
   974  			params: UpdateIncludeRuleTreeRequest{
   975  				IncludeID:     "inc_123456",
   976  				ContractID:    "test_contract",
   977  				GroupID:       "test_group",
   978  				DryRun:        true,
   979  				ValidateMode:  "fast",
   980  				ValidateRules: false,
   981  				Rules: RulesUpdate{
   982  					Comments: "version comment",
   983  					Rules: Rules{
   984  						Comments: "default comment",
   985  						Behaviors: []RuleBehavior{
   986  							{
   987  								Name: "origin",
   988  								Options: RuleOptionsMap{
   989  									"httpPort":           float64(80),
   990  									"enableTrueClientIp": false,
   991  									"compress":           true,
   992  									"cacheKeyHostname":   "ORIGIN_HOSTNAME",
   993  									"forwardHostHeader":  "REQUEST_HOST_HEADER",
   994  									"hostname":           "origin.test.com",
   995  									"originType":         "CUSTOMER",
   996  								},
   997  							},
   998  							{
   999  								Name: "cpCode",
  1000  								Options: RuleOptionsMap{
  1001  									"value": map[string]interface{}{
  1002  										"id":   float64(12345),
  1003  										"name": "my CP code",
  1004  									},
  1005  								},
  1006  							},
  1007  						},
  1008  						Children: []Rules{
  1009  							{
  1010  								Behaviors: []RuleBehavior{
  1011  									{
  1012  										Name: "gzipResponse",
  1013  										Options: RuleOptionsMap{
  1014  											"behavior": "ALWAYS",
  1015  										},
  1016  									},
  1017  								},
  1018  								Criteria: []RuleBehavior{
  1019  									{
  1020  										Locked: false,
  1021  										Name:   "contentType",
  1022  										Options: RuleOptionsMap{
  1023  											"matchOperator":      "IS_ONE_OF",
  1024  											"matchWildcard":      true,
  1025  											"matchCaseSensitive": false,
  1026  											"values":             []interface{}{"text/html*", "text/css*", "application/x-javascript*"},
  1027  										},
  1028  									},
  1029  								},
  1030  								Name: "Compress Text Content",
  1031  							},
  1032  						},
  1033  						Criteria: []RuleBehavior{},
  1034  						Name:     "default",
  1035  						Options:  RuleOptions{IsSecure: false},
  1036  						CustomOverride: &RuleCustomOverride{
  1037  							OverrideID: "cbo_12345",
  1038  							Name:       "mdc",
  1039  						},
  1040  						Variables: []RuleVariable{
  1041  							{
  1042  								Description: tools.StringPtr("This is a sample Property Manager variable."),
  1043  								Hidden:      false,
  1044  								Name:        "VAR_NAME",
  1045  								Sensitive:   false,
  1046  								Value:       tools.StringPtr("default value"),
  1047  							},
  1048  						},
  1049  					},
  1050  				},
  1051  			},
  1052  			withError: ErrStructValidation,
  1053  		},
  1054  		"validation error - missing contractId": {
  1055  			params: UpdateIncludeRuleTreeRequest{
  1056  				IncludeID:      "inc_123456",
  1057  				IncludeVersion: 2,
  1058  				GroupID:        "test_group",
  1059  				DryRun:         true,
  1060  				ValidateMode:   "fast",
  1061  				ValidateRules:  false,
  1062  				Rules: RulesUpdate{
  1063  					Comments: "version comment",
  1064  					Rules: Rules{
  1065  						Comments: "default comment",
  1066  						Behaviors: []RuleBehavior{
  1067  							{
  1068  								Name: "origin",
  1069  								Options: RuleOptionsMap{
  1070  									"httpPort":           float64(80),
  1071  									"enableTrueClientIp": false,
  1072  									"compress":           true,
  1073  									"cacheKeyHostname":   "ORIGIN_HOSTNAME",
  1074  									"forwardHostHeader":  "REQUEST_HOST_HEADER",
  1075  									"hostname":           "origin.test.com",
  1076  									"originType":         "CUSTOMER",
  1077  								},
  1078  							},
  1079  							{
  1080  								Name: "cpCode",
  1081  								Options: RuleOptionsMap{
  1082  									"value": map[string]interface{}{
  1083  										"id":   float64(12345),
  1084  										"name": "my CP code",
  1085  									},
  1086  								},
  1087  							},
  1088  						},
  1089  						Children: []Rules{
  1090  							{
  1091  								Behaviors: []RuleBehavior{
  1092  									{
  1093  										Name: "gzipResponse",
  1094  										Options: RuleOptionsMap{
  1095  											"behavior": "ALWAYS",
  1096  										},
  1097  									},
  1098  								},
  1099  								Criteria: []RuleBehavior{
  1100  									{
  1101  										Locked: false,
  1102  										Name:   "contentType",
  1103  										Options: RuleOptionsMap{
  1104  											"matchOperator":      "IS_ONE_OF",
  1105  											"matchWildcard":      true,
  1106  											"matchCaseSensitive": false,
  1107  											"values":             []interface{}{"text/html*", "text/css*", "application/x-javascript*"},
  1108  										},
  1109  									},
  1110  								},
  1111  								Name: "Compress Text Content",
  1112  							},
  1113  						},
  1114  						Criteria: []RuleBehavior{},
  1115  						Name:     "default",
  1116  						Options:  RuleOptions{IsSecure: false},
  1117  						CustomOverride: &RuleCustomOverride{
  1118  							OverrideID: "cbo_12345",
  1119  							Name:       "mdc",
  1120  						},
  1121  						Variables: []RuleVariable{
  1122  							{
  1123  								Description: tools.StringPtr("This is a sample Property Manager variable."),
  1124  								Hidden:      false,
  1125  								Name:        "VAR_NAME",
  1126  								Sensitive:   false,
  1127  								Value:       tools.StringPtr("default value"),
  1128  							},
  1129  						},
  1130  					},
  1131  				},
  1132  			},
  1133  			withError: ErrStructValidation,
  1134  		},
  1135  		"validation error - missing groupId": {
  1136  			params: UpdateIncludeRuleTreeRequest{
  1137  				IncludeID:      "inc_123456",
  1138  				IncludeVersion: 2,
  1139  				ContractID:     "test_contract",
  1140  				DryRun:         true,
  1141  				ValidateMode:   "fast",
  1142  				ValidateRules:  false,
  1143  				Rules: RulesUpdate{
  1144  					Comments: "version comment",
  1145  					Rules: Rules{
  1146  						Comments: "default comment",
  1147  						Behaviors: []RuleBehavior{
  1148  							{
  1149  								Name: "origin",
  1150  								Options: RuleOptionsMap{
  1151  									"httpPort":           float64(80),
  1152  									"enableTrueClientIp": false,
  1153  									"compress":           true,
  1154  									"cacheKeyHostname":   "ORIGIN_HOSTNAME",
  1155  									"forwardHostHeader":  "REQUEST_HOST_HEADER",
  1156  									"hostname":           "origin.test.com",
  1157  									"originType":         "CUSTOMER",
  1158  								},
  1159  							},
  1160  							{
  1161  								Name: "cpCode",
  1162  								Options: RuleOptionsMap{
  1163  									"value": map[string]interface{}{
  1164  										"id":   float64(12345),
  1165  										"name": "my CP code",
  1166  									},
  1167  								},
  1168  							},
  1169  						},
  1170  						Children: []Rules{
  1171  							{
  1172  								Behaviors: []RuleBehavior{
  1173  									{
  1174  										Name: "gzipResponse",
  1175  										Options: RuleOptionsMap{
  1176  											"behavior": "ALWAYS",
  1177  										},
  1178  									},
  1179  								},
  1180  								Criteria: []RuleBehavior{
  1181  									{
  1182  										Locked: false,
  1183  										Name:   "contentType",
  1184  										Options: RuleOptionsMap{
  1185  											"matchOperator":      "IS_ONE_OF",
  1186  											"matchWildcard":      true,
  1187  											"matchCaseSensitive": false,
  1188  											"values":             []interface{}{"text/html*", "text/css*", "application/x-javascript*"},
  1189  										},
  1190  									},
  1191  								},
  1192  								Name: "Compress Text Content",
  1193  							},
  1194  						},
  1195  						Criteria: []RuleBehavior{},
  1196  						Name:     "default",
  1197  						Options:  RuleOptions{IsSecure: false},
  1198  						CustomOverride: &RuleCustomOverride{
  1199  							OverrideID: "cbo_12345",
  1200  							Name:       "mdc",
  1201  						},
  1202  						Variables: []RuleVariable{
  1203  							{
  1204  								Description: tools.StringPtr("This is a sample Property Manager variable."),
  1205  								Hidden:      false,
  1206  								Name:        "VAR_NAME",
  1207  								Sensitive:   false,
  1208  								Value:       tools.StringPtr("default value"),
  1209  							},
  1210  						},
  1211  					},
  1212  				},
  1213  			},
  1214  			withError: ErrStructValidation,
  1215  		},
  1216  		"validation error - invalid validation mode": {
  1217  			params: UpdateIncludeRuleTreeRequest{
  1218  				IncludeID:      "inc_123456",
  1219  				IncludeVersion: 2,
  1220  				ContractID:     "test_contract",
  1221  				GroupID:        "test_group",
  1222  				DryRun:         true,
  1223  				ValidateMode:   "test",
  1224  				ValidateRules:  false,
  1225  				Rules: RulesUpdate{
  1226  					Comments: "version comment",
  1227  					Rules: Rules{
  1228  						Comments: "default comment",
  1229  						Behaviors: []RuleBehavior{
  1230  							{
  1231  								Name: "origin",
  1232  								Options: RuleOptionsMap{
  1233  									"httpPort":           float64(80),
  1234  									"enableTrueClientIp": false,
  1235  									"compress":           true,
  1236  									"cacheKeyHostname":   "ORIGIN_HOSTNAME",
  1237  									"forwardHostHeader":  "REQUEST_HOST_HEADER",
  1238  									"hostname":           "origin.test.com",
  1239  									"originType":         "CUSTOMER",
  1240  								},
  1241  							},
  1242  							{
  1243  								Name: "cpCode",
  1244  								Options: RuleOptionsMap{
  1245  									"value": map[string]interface{}{
  1246  										"id":   float64(12345),
  1247  										"name": "my CP code",
  1248  									},
  1249  								},
  1250  							},
  1251  						},
  1252  						Children: []Rules{
  1253  							{
  1254  								Behaviors: []RuleBehavior{
  1255  									{
  1256  										Name: "gzipResponse",
  1257  										Options: RuleOptionsMap{
  1258  											"behavior": "ALWAYS",
  1259  										},
  1260  									},
  1261  								},
  1262  								Criteria: []RuleBehavior{
  1263  									{
  1264  										Locked: false,
  1265  										Name:   "contentType",
  1266  										Options: RuleOptionsMap{
  1267  											"matchOperator":      "IS_ONE_OF",
  1268  											"matchWildcard":      true,
  1269  											"matchCaseSensitive": false,
  1270  											"values":             []interface{}{"text/html*", "text/css*", "application/x-javascript*"},
  1271  										},
  1272  									},
  1273  								},
  1274  								Name: "Compress Text Content",
  1275  							},
  1276  						},
  1277  						Criteria: []RuleBehavior{},
  1278  						Name:     "default",
  1279  						Options:  RuleOptions{IsSecure: false},
  1280  						CustomOverride: &RuleCustomOverride{
  1281  							OverrideID: "cbo_12345",
  1282  							Name:       "mdc",
  1283  						},
  1284  						Variables: []RuleVariable{
  1285  							{
  1286  								Description: tools.StringPtr("This is a sample Property Manager variable."),
  1287  								Hidden:      false,
  1288  								Name:        "VAR_NAME",
  1289  								Sensitive:   false,
  1290  								Value:       tools.StringPtr("default value"),
  1291  							},
  1292  						},
  1293  					},
  1294  				},
  1295  			},
  1296  			withError: ErrStructValidation,
  1297  		},
  1298  	}
  1299  
  1300  	for name, test := range tests {
  1301  		t.Run(name, func(t *testing.T) {
  1302  			mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  1303  				assert.Equal(t, test.expectedPath, r.URL.String())
  1304  				assert.Equal(t, http.MethodPut, r.Method)
  1305  
  1306  				if len(test.responseHeaders) > 0 {
  1307  					for header, value := range test.responseHeaders {
  1308  						w.Header().Set(header, value)
  1309  					}
  1310  				}
  1311  				w.WriteHeader(test.responseStatus)
  1312  				_, err := w.Write([]byte(test.responseBody))
  1313  				assert.NoError(t, err)
  1314  			}))
  1315  			client := mockAPIClient(t, mockServer)
  1316  			result, err := client.UpdateIncludeRuleTree(context.Background(), test.params)
  1317  			if test.withError != nil {
  1318  				assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err)
  1319  				return
  1320  			}
  1321  			require.NoError(t, err)
  1322  			assert.Equal(t, test.expectedResponse, result)
  1323  		})
  1324  	}
  1325  }