Repository: cloudflare/cloudflare-go
Pull request #1459 (2023-12-06 01:07:16)

Adding Page Shield support
## Description
Added support for Page Shield based on developer docs.

## Has your change been tested?
All changes have been tested with test suites. Also, have tested using a local driver application 

## Screenshots (if appropriate):
![Screenshot 2023-12-05 at 7 05 17 PM](https://github.com/cloudflare/cloudflare-go/assets/12678751/8c5e5a48-530e-4813-8ea5-b0899cf5ac2c)

## Types of changes

What sort of change does your code introduce/modify?

- [ ] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to change)

## Checklist:

- [x] My code follows the code style of this project.
- [ ] My change requires a change to the documentation.
- [ ] I have updated the documentation accordingly.
- [x] I have added tests to cover my changes.
- [x] All new and existing tests passed.
- [x] This change is using publicly documented in [cloudflare/api-schemas](https://github.com/cloudflare/api-schemas) 
      and relies on stable APIs.

[1]: https://help.github.com/articles/closing-issues-using-keywords/


github-actions[bot]: Oops! It looks like no changelog entry is attached to this PR. Please include a release note as described in https://github.com/cloudflare/cloudflare-go/blob/master/docs/changelog-process.md.

Example: 

~~~
```release-note:TYPE
Release note
```
~~~

If you do not require a release note to be included, please add the `workflow/skip-changelog-entry` label.

Patch:
diff --git a/.changelog/1445.txt b/.changelog/1445.txt
deleted file mode 100644
index 664bb2d3e..000000000
--- a/.changelog/1445.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-```release-note:enhancement
-access_group: Add support for email lists
-```
diff --git a/.changelog/1459.txt b/.changelog/1459.txt
new file mode 100644
index 000000000..b7d1a74e4
--- /dev/null
+++ b/.changelog/1459.txt
@@ -0,0 +1,3 @@
+```release-note:enhancement
+page_shield: added support for page shield
+```
\ No newline at end of file
diff --git a/.changelog/1460.txt b/.changelog/1460.txt
deleted file mode 100644
index 0bf5ed5f5..000000000
--- a/.changelog/1460.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-```release-note:dependency
-deps: bumps actions/setup-go from 4 to 5
-```
diff --git a/.github/workflows/changelog-check.yml b/.github/workflows/changelog-check.yml
index fe273f235..567ed6cf4 100644
--- a/.github/workflows/changelog-check.yml
+++ b/.github/workflows/changelog-check.yml
@@ -7,7 +7,7 @@ jobs:
     steps:
       - uses: actions/checkout@v4
       - name: Set up Go
-        uses: actions/setup-go@v5
+        uses: actions/setup-go@v4
         with:
           go-version-file: 'internal/tools/go.mod'
       - run: go generate -tags tools internal/tools/tools.go
diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml
index b01e83d50..375176286 100644
--- a/.github/workflows/coverage.yml
+++ b/.github/workflows/coverage.yml
@@ -6,7 +6,7 @@ jobs:
     steps:
       - name: Checkout code
         uses: actions/checkout@v4
-      - uses: actions/setup-go@v5
+      - uses: actions/setup-go@v4
         with:
           go-version-file: 'go.mod'
       - uses: actions/cache@v3
diff --git a/.github/workflows/generate-changelog.yml b/.github/workflows/generate-changelog.yml
index 73de29182..27546303e 100644
--- a/.github/workflows/generate-changelog.yml
+++ b/.github/workflows/generate-changelog.yml
@@ -11,7 +11,7 @@ jobs:
       - uses: actions/checkout@v4
         with:
           fetch-depth: 0
-      - uses: actions/setup-go@v5
+      - uses: actions/setup-go@v4
         with:
           go-version-file: 'internal/tools/go.mod'
       - run: go generate -tags tools internal/tools/tools.go
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 096ac5242..8f943df50 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -13,7 +13,7 @@ jobs:
         uses: actions/checkout@v4
         with:
           fetch-depth: 0
-      - uses: actions/setup-go@v5
+      - uses: actions/setup-go@v4
         with:
           go-version-file: 'go.mod'
       - name: Run GoReleaser
diff --git a/.golintci.yaml b/.golintci.yaml
index d5d1c9397..c4e5df2c6 100644
--- a/.golintci.yaml
+++ b/.golintci.yaml
@@ -27,7 +27,3 @@ linters:
 
 output:
   format: colored-line-number
-
-linters-settings:
-  goconst:
-    ignore-tests: true
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 92a6ad7cc..386b09e22 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,13 +1,5 @@
 ## 0.84.0 (Unreleased)
 
-ENHANCEMENTS:
-
-* access_group: Add support for email lists ([#1445](https://github.com/cloudflare/cloudflare-go/issues/1445))
-
-DEPENDENCIES:
-
-* deps: bumps actions/setup-go from 4 to 5 ([#1460](https://github.com/cloudflare/cloudflare-go/issues/1460))
-
 ## 0.83.0 (December 6th, 2023)
 
 ENHANCEMENTS:
diff --git a/access_group.go b/access_group.go
index a521db8e0..bdb3899ad 100644
--- a/access_group.go
+++ b/access_group.go
@@ -39,15 +39,6 @@ type AccessGroupEmail struct {
 	} `json:"email"`
 }
 
-// AccessGroupEmailList is used for managing access based on the email
-// list. For example, restrict access to users with the email addresses
-// in the email list with the ID `1234567890abcdef1234567890abcdef`.
-type AccessGroupEmailList struct {
-	EmailList struct {
-		ID string `json:"id"`
-	} `json:"email_list"`
-}
-
 // AccessGroupEmailDomain is used for managing access based on an email
 // domain such as `example.com` instead of individual addresses.
 type AccessGroupEmailDomain struct {
diff --git a/page_shield.go b/page_shield.go
new file mode 100644
index 000000000..d05c94972
--- /dev/null
+++ b/page_shield.go
@@ -0,0 +1,76 @@
+package cloudflare
+
+import (
+	"context"
+	"fmt"
+	"net/http"
+
+	"github.com/goccy/go-json"
+)
+
+// PageShield represents the page shield object minus any timestamps.
+type PageShield struct {
+	Enabled                        *bool `json:"enabled,omitempty"`
+	UseCloudflareReportingEndpoint *bool `json:"use_cloudflare_reporting_endpoint,omitempty"`
+	UseConnectionURLPath           *bool `json:"use_connection_url_path,omitempty"`
+}
+
+type UpdatePageShieldSettingsParams struct {
+	Enabled                        *bool `json:"enabled,omitempty"`
+	UseCloudflareReportingEndpoint *bool `json:"use_cloudflare_reporting_endpoint,omitempty"`
+	UseConnectionURLPath           *bool `json:"use_connection_url_path,omitempty"`
+}
+
+// PageShieldSettings represents the page shield settings for a zone.
+type PageShieldSettings struct {
+	PageShield
+	UpdatedAt string `json:"updated_at"`
+}
+
+// PageShieldSettingsResponse represents the response from the page shield settings endpoint.
+type PageShieldSettingsResponse struct {
+	PageShield PageShieldSettings `json:"result"`
+	Response
+}
+
+type GetPageShieldSettingsParams struct{}
+
+// GetPageShieldSettings returns the page shield settings for a zone.
+//
+// API documentation: https://developers.cloudflare.com/api/operations/page-shield-get-page-shield-settings
+func (api *API) GetPageShieldSettings(ctx context.Context, rc *ResourceContainer, params GetPageShieldSettingsParams) (*PageShieldSettingsResponse, error) {
+	uri := fmt.Sprintf("/zones/%s/page_shield", rc.Identifier)
+
+	res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
+	if err != nil {
+		return nil, err
+	}
+
+	var psResponse PageShieldSettingsResponse
+	err = json.Unmarshal(res, &psResponse)
+	if err != nil {
+		return nil, fmt.Errorf("%s: %w", errUnmarshalError, err)
+	}
+
+	return &psResponse, nil
+}
+
+// UpdatePageShieldSettings updates the page shield settings for a zone.
+//
+// API documentation: https://developers.cloudflare.com/api/operations/page-shield-update-page-shield-settings
+func (api *API) UpdatePageShieldSettings(ctx context.Context, rc *ResourceContainer, params UpdatePageShieldSettingsParams) (*PageShieldSettingsResponse, error) {
+	uri := fmt.Sprintf("/zones/%s/page_shield", rc.Identifier)
+
+	res, err := api.makeRequestContext(ctx, http.MethodPut, uri, params)
+	if err != nil {
+		return nil, err
+	}
+
+	var psResponse PageShieldSettingsResponse
+	err = json.Unmarshal(res, &psResponse)
+	if err != nil {
+		return nil, fmt.Errorf("%s: %w", errUnmarshalError, err)
+	}
+
+	return &psResponse, nil
+}
diff --git a/page_shield_connections.go b/page_shield_connections.go
new file mode 100644
index 000000000..904651276
--- /dev/null
+++ b/page_shield_connections.go
@@ -0,0 +1,88 @@
+package cloudflare
+
+import (
+	"context"
+	"fmt"
+	"net/http"
+
+	"github.com/goccy/go-json"
+)
+
+// ListPageShieldConnectionsParams represents parameters for a page shield connection request.
+type ListPageShieldConnectionsParams struct {
+	Direction           string `url:"direction"`
+	ExcludeCdnCgi       *bool  `url:"exclude_cdn_cgi,omitempty"`
+	ExcludeUrls         string `url:"exclude_urls"`
+	Export              string `url:"export"`
+	Hosts               string `url:"hosts"`
+	OrderBy             string `url:"order_by"`
+	Page                string `url:"page"`
+	PageURL             string `url:"page_url"`
+	PerPage             int    `url:"per_page"`
+	PrioritizeMalicious *bool  `url:"prioritize_malicious,omitempty"`
+	Status              string `url:"status"`
+	URLs                string `url:"urls"`
+}
+
+// PageShieldConnection represents a page shield connection.
+type PageShieldConnection struct {
+	AddedAt                 string   `json:"added_at"`
+	DomainReportedMalicious *bool    `json:"domain_reported_malicious,omitempty"`
+	FirstPageURL            string   `json:"first_page_url"`
+	FirstSeenAt             string   `json:"first_seen_at"`
+	Host                    string   `json:"host"`
+	ID                      string   `json:"id"`
+	LastSeenAt              string   `json:"last_seen_at"`
+	PageURLs                []string `json:"page_urls"`
+	URL                     string   `json:"url"`
+	URLContainsCdnCgiPath   *bool    `json:"url_contains_cdn_cgi_path,omitempty"`
+}
+
+// ListPageShieldConnectionsResponse represents the response from the list page shield connections endpoint.
+type ListPageShieldConnectionsResponse struct {
+	Result []PageShieldConnection `json:"result"`
+	Response
+	ResultInfo `json:"result_info"`
+}
+
+// ListPageShieldConnections lists all page shield connections for a zone.
+//
+// API documentation: https://developers.cloudflare.com/api/operations/page-shield-list-page-shield-connections
+func (api *API) ListPageShieldConnections(ctx context.Context, rc *ResourceContainer, params ListPageShieldConnectionsParams) ([]PageShieldConnection, ResultInfo, error) {
+	path := fmt.Sprintf("/zones/%s/page_shield/connections", rc.Identifier)
+
+	uri := buildURI(path, params)
+
+	res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
+	if err != nil {
+		return nil, ResultInfo{}, err
+	}
+
+	var psResponse ListPageShieldConnectionsResponse
+	err = json.Unmarshal(res, &psResponse)
+	if err != nil {
+		return nil, ResultInfo{}, fmt.Errorf("%s: %w", errUnmarshalError, err)
+	}
+
+	return psResponse.Result, psResponse.ResultInfo, nil
+}
+
+// GetPageShieldConnection gets a page shield connection for a zone.
+//
+// API documentation: https://developers.cloudflare.com/api/operations/page-shield-get-a-page-shield-connection
+func (api *API) GetPageShieldConnection(ctx context.Context, rc *ResourceContainer, connectionID string) (*PageShieldConnection, error) {
+	path := fmt.Sprintf("/zones/%s/page_shield/connections/%s", rc.Identifier, connectionID)
+
+	res, err := api.makeRequestContext(ctx, http.MethodGet, path, nil)
+	if err != nil {
+		return nil, err
+	}
+
+	var psResponse PageShieldConnection
+	err = json.Unmarshal(res, &psResponse)
+	if err != nil {
+		return nil, fmt.Errorf("%s: %w", errUnmarshalError, err)
+	}
+
+	return &psResponse, nil
+}
diff --git a/page_shield_policies.go b/page_shield_policies.go
new file mode 100644
index 000000000..29ce64ad0
--- /dev/null
+++ b/page_shield_policies.go
@@ -0,0 +1,140 @@
+package cloudflare
+
+import (
+	"context"
+	"fmt"
+	"net/http"
+
+	"github.com/goccy/go-json"
+)
+
+// PageShieldPolicy represents a page shield policy.
+type PageShieldPolicy struct {
+	Action      string `json:"action"`
+	Description string `json:"description"`
+	Enabled     *bool  `json:"enabled,omitempty"`
+	Expression  string `json:"expression"`
+	ID          string `json:"id"`
+	Value       string `json:"value"`
+}
+
+type CreatePageShieldPolicyParams struct {
+	Action      string `json:"action"`
+	Description string `json:"description"`
+	Enabled     *bool  `json:"enabled,omitempty"`
+	Expression  string `json:"expression"`
+	ID          string `json:"id"`
+	Value       string `json:"value"`
+}
+
+type UpdatePageShieldPolicyParams struct {
+	Action      string `json:"action"`
+	Description string `json:"description"`
+	Enabled     *bool  `json:"enabled,omitempty"`
+	Expression  string `json:"expression"`
+	ID          string `json:"id"`
+	Value       string `json:"value"`
+}
+
+// ListPageShieldPoliciesResponse represents the response from the list page shield policies endpoint.
+type ListPageShieldPoliciesResponse struct {
+	Result []PageShieldPolicy `json:"result"`
+	Response
+	ResultInfo `json:"result_info"`
+}
+
+type ListPageShieldPoliciesParams struct{}
+
+// ListPageShieldPolicies lists all page shield policies for a zone.
+//
+// API documentation: https://developers.cloudflare.com/api/operations/page-shield-list-page-shield-policies
+func (api *API) ListPageShieldPolicies(ctx context.Context, rc *ResourceContainer, params ListPageShieldPoliciesParams) ([]PageShieldPolicy, ResultInfo, error) {
+	path := fmt.Sprintf("/zones/%s/page_shield/policies", rc.Identifier)
+
+	res, err := api.makeRequestContext(ctx, http.MethodGet, path, nil)
+	if err != nil {
+		return nil, ResultInfo{}, err
+	}
+
+	var psResponse ListPageShieldPoliciesResponse
+	err = json.Unmarshal(res, &psResponse)
+	if err != nil {
+		return nil, ResultInfo{}, fmt.Errorf("%s: %w", errUnmarshalError, err)
+	}
+
+	return psResponse.Result, psResponse.ResultInfo, nil
+}
+
+// CreatePageShieldPolicy creates a page shield policy for a zone.
+//
+// API documentation: https://developers.cloudflare.com/api/operations/page-shield-create-page-shield-policy
+func (api *API) CreatePageShieldPolicy(ctx context.Context, rc *ResourceContainer, params CreatePageShieldPolicyParams) (*PageShieldPolicy, error) {
+	path := fmt.Sprintf("/zones/%s/page_shield/policies", rc.Identifier)
+
+	res, err := api.makeRequestContext(ctx, http.MethodPost, path, params)
+	if err != nil {
+		return nil, err
+	}
+
+	var psResponse PageShieldPolicy
+	err = json.Unmarshal(res, &psResponse)
+	if err != nil {
+		return nil, fmt.Errorf("%s: %w", errUnmarshalError, err)
+	}
+
+	return &psResponse, nil
+}
+
+// DeletePageShieldPolicy deletes a page shield policy for a zone.
+//
+// API documentation: https://developers.cloudflare.com/api/operations/page-shield-delete-page-shield-policy
+func (api *API) DeletePageShieldPolicy(ctx context.Context, rc *ResourceContainer, policyID string) error {
+	path := fmt.Sprintf("/zones/%s/page_shield/policies/%s", rc.Identifier, policyID)
+
+	_, err := api.makeRequestContext(ctx, http.MethodDelete, path, nil)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// GetPageShieldPolicy gets a page shield policy for a zone.
+//
+// API documentation: https://developers.cloudflare.com/api/operations/page-shield-get-page-shield-policy
+func (api *API) GetPageShieldPolicy(ctx context.Context, rc *ResourceContainer, policyID string) (*PageShieldPolicy, error) {
+	path := fmt.Sprintf("/zones/%s/page_shield/policies/%s", rc.Identifier, policyID)
+
+	res, err := api.makeRequestContext(ctx, http.MethodGet, path, nil)
+	if err != nil {
+		return nil, err
+	}
+
+	var psResponse PageShieldPolicy
+	err = json.Unmarshal(res, &psResponse)
+	if err != nil {
+		return nil, fmt.Errorf("%s: %w", errUnmarshalError, err)
+	}
+
+	return &psResponse, nil
+}
+
+// UpdatePageShieldPolicy updates a page shield policy for a zone.
+//
+// API documentation: https://developers.cloudflare.com/api/operations/page-shield-update-page-shield-policy
+func (api *API) UpdatePageShieldPolicy(ctx context.Context, rc *ResourceContainer, params UpdatePageShieldPolicyParams) (*PageShieldPolicy, error) {
+	path := fmt.Sprintf("/zones/%s/page_shield/policies/%s", rc.Identifier, params.ID)
+
+	res, err := api.makeRequestContext(ctx, http.MethodPut, path, params)
+	if err != nil {
+		return nil, err
+	}
+
+	var psResponse PageShieldPolicy
+	err = json.Unmarshal(res, &psResponse)
+	if err != nil {
+		return nil, fmt.Errorf("%s: %w", errUnmarshalError, err)
+	}
+
+	return &psResponse, nil
+}
diff --git a/page_shield_scripts.go b/page_shield_scripts.go
new file mode 100644
index 000000000..09559b59f
--- /dev/null
+++ b/page_shield_scripts.go
@@ -0,0 +1,107 @@
+package cloudflare
+
+import (
+	"context"
+	"fmt"
+	"net/http"
+
+	"github.com/goccy/go-json"
+)
+
+// PageShieldScript represents a Page Shield script.
+type PageShieldScript struct {
+	AddedAt                 string   `json:"added_at"`
+	DomainReportedMalicious *bool    `json:"domain_reported_malicious,omitempty"`
+	FetchedAt               string   `json:"fetched_at"`
+	FirstPageURL            string   `json:"first_page_url"`
+	FirstSeenAt             string   `json:"first_seen_at"`
+	Hash                    string   `json:"hash"`
+	Host                    string   `json:"host"`
+	ID                      string   `json:"id"`
+	JSIntegrityScore        int      `json:"js_integrity_score"`
+	LastSeenAt              string   `json:"last_seen_at"`
+	PageURLs                []string `json:"page_urls"`
+	URL                     string   `json:"url"`
+	URLContainsCdnCgiPath   *bool    `json:"url_contains_cdn_cgi_path,omitempty"`
+}
+
+// ListPageShieldScriptsParams represents a PageShield Script request parameters.
+//
+// API reference: https://developers.cloudflare.com/api/operations/page-shield-list-page-shield-scripts#Query-Parameters
+type ListPageShieldScriptsParams struct {
+	Direction           string `url:"direction"`
+	ExcludeCdnCgi       *bool  `url:"exclude_cdn_cgi,omitempty"`
+	ExcludeDuplicates   *bool  `url:"exclude_duplicates,omitempty"`
+	ExcludeUrls         string `url:"exclude_urls"`
+	Export              string `url:"export"`
+	Hosts               string `url:"hosts"`
+	OrderBy             string `url:"order_by"`
+	Page                string `url:"page"`
+	PageURL             string `url:"page_url"`
+	PerPage             int    `url:"per_page"`
+	PrioritizeMalicious *bool  `url:"prioritize_malicious,omitempty"`
+	Status              string `url:"status"`
+	URLs                string `url:"urls"`
+}
+
+// PageShieldScriptsResponse represents the response from the PageShield Script API.
+type PageShieldScriptsResponse struct {
+	Results []PageShieldScript `json:"result"`
+	Response
+	ResultInfo `json:"result_info"`
+}
+
+// PageShieldScriptResponse represents the response from the PageShield Script API.
+type PageShieldScriptResponse struct {
+	Result   PageShieldScript          `json:"result"`
+	Versions []PageShieldScriptVersion `json:"versions"`
+}
+
+// PageShieldScriptVersion represents a Page Shield script version.
+type PageShieldScriptVersion struct {
+	FetchedAt        string `json:"fetched_at"`
+	Hash             string `json:"hash"`
+	JSIntegrityScore int    `json:"js_integrity_score"`
+}
+
+// ListPageShieldScripts returns a list of PageShield Scripts.
+//
+// API reference: https://developers.cloudflare.com/api/operations/page-shield-list-page-shield-scripts
+func (api *API) ListPageShieldScripts(ctx context.Context, rc *ResourceContainer, params ListPageShieldScriptsParams) ([]PageShieldScript, ResultInfo, error) {
+	path := fmt.Sprintf("/zones/%s/page_shield/scripts", rc.Identifier)
+
+	uri := buildURI(path, params)
+
+	res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
+	if err != nil {
+		return nil, ResultInfo{}, err
+	}
+
+	var psResponse PageShieldScriptsResponse
+	err = json.Unmarshal(res, &psResponse)
+	if err != nil {
+		return nil, ResultInfo{}, fmt.Errorf("%s: %w", errUnmarshalError, err)
+	}
+
+	return psResponse.Results, psResponse.ResultInfo, nil
+}
+
+// GetPageShieldScript returns a PageShield Script.
+//
+// API reference: https://developers.cloudflare.com/api/operations/page-shield-get-a-page-shield-script
+func (api *API) GetPageShieldScript(ctx context.Context, rc *ResourceContainer, scriptID string) (*PageShieldScript, []PageShieldScriptVersion, error) {
+	path := fmt.Sprintf("/zones/%s/page_shield/scripts/%s", rc.Identifier, scriptID)
+
+	res, err := api.makeRequestContext(ctx, http.MethodGet, path, nil)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var psResponse PageShieldScriptResponse
+	err = json.Unmarshal(res, &psResponse)
+	if err != nil {
+		return nil, nil, fmt.Errorf("%s: %w", errUnmarshalError, err)
+	}
+
+	return &psResponse.Result, psResponse.Versions, nil
+}


Test patch:
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 9ca59aba2..b7181ed6a 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -7,7 +7,7 @@ jobs:
         go-version: ["1.19", "1.20", "1.21"]
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/setup-go@v5
+      - uses: actions/setup-go@v4
         with:
           go-version: ${{ matrix.go-version }}
       - name: Checkout code
diff --git a/access_group_test.go b/access_group_test.go
index 3acb9acae..16bbd436e 100644
--- a/access_group_test.go
+++ b/access_group_test.go
@@ -37,16 +37,6 @@ var (
 			map[string]interface{}{"ip_list": map[string]interface{}{"id": "989d98642c564d2e855e9661899b7252"}},
 		},
 	}
-
-	expectedAccessGroupEmailList = AccessGroup{
-		ID:        "a99d98642c564d2e855e9661899b7252",
-		CreatedAt: &createdAt,
-		UpdatedAt: &updatedAt,
-		Name:      "Allow devs",
-		Include: []interface{}{
-			map[string]interface{}{"email_list": map[string]interface{}{"id": "8a9d98642c564d2e855e9661899b7252"}},
-		},
-	}
 )
 
 func TestAccessGroups(t *testing.T) {
@@ -416,56 +406,3 @@ func TestCreateIPListAccessGroup(t *testing.T) {
 		assert.Equal(t, expectedAccessGroupIpList, actual)
 	}
 }
-
-func TestCreateEmailListAccessGroup(t *testing.T) {
-	setup()
-	defer teardown()
-
-	handler := func(w http.ResponseWriter, r *http.Request) {
-		assert.Equal(t, http.MethodPost, r.Method, "Expected method 'POST', got %s", r.Method)
-		w.Header().Set("content-type", "application/json")
-
-		fmt.Fprintf(w, `{
-			"success": true,
-			"errors": [],
-			"messages": [],
-			"result": {
-				"id": "a99d98642c564d2e855e9661899b7252",
-				"created_at": "2014-01-01T05:20:00.12345Z",
-				"updated_at": "2014-01-01T05:20:00.12345Z",
-				"name": "Allow devs",
-				"include": [
-					{
-						"email_list": {
-							"id": "8a9d98642c564d2e855e9661899b7252"
-						}
-					}
-				]
-			}
-		}
-		`)
-	}
-
-	mux.HandleFunc("/accounts/"+testAccountID+"/access/groups", handler)
-
-	accessGroup := CreateAccessGroupParams{
-		Name: "Allow devs by email_list",
-		Include: []interface{}{
-			AccessGroupEmailList{struct {
-				ID string `json:"id"`
-			}{ID: "989d98642c564d2e855e9661899b7252"}},
-		},
-	}
-
-	actual, err := client.CreateAccessGroup(context.Background(), testAccountRC, accessGroup)
-	if assert.NoError(t, err) {
-		assert.Equal(t, expectedAccessGroupEmailList, actual)
-	}
-
-	mux.HandleFunc("/zones/"+testZoneID+"/access/groups", handler)
-
-	actual, err = client.CreateAccessGroup(context.Background(), testZoneRC, accessGroup)
-	if assert.NoError(t, err) {
-		assert.Equal(t, expectedAccessGroupEmailList, actual)
-	}
-}
diff --git a/page_shield_connections_test.go b/page_shield_connections_test.go
new file mode 100644
index 000000000..42e78cc63
--- /dev/null
+++ b/page_shield_connections_test.go
@@ -0,0 +1,90 @@
+package cloudflare
+
+import (
+	"context"
+	"fmt"
+	"net/http"
+	"testing"
+
+	"github.com/goccy/go-json"
+	"github.com/stretchr/testify/assert"
+)
+
+// Mock data for PageShieldConnections.
+var mockPageShieldConnections = []PageShieldConnection{
+	{
+		AddedAt:                 "2021-08-18T10:51:10.09615Z",
+		DomainReportedMalicious: BoolPtr(false),
+		FirstPageURL:            "blog.cloudflare.com/page",
+		FirstSeenAt:             "2021-08-18T10:51:08Z",
+		Host:                    "blog.cloudflare.com",
+		ID:                      "c9ef84a6bf5e47138c75d95e2f933e8f",
+		LastSeenAt:              "2021-09-02T09:57:54Z",
+		PageURLs:                []string{"blog.cloudflare.com/page1", "blog.cloudflare.com/page2"},
+		URL:                     "https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.6.0/js/bootstrap.min.js",
+		URLContainsCdnCgiPath:   BoolPtr(false),
+	},
+	{
+		AddedAt:                 "2021-09-18T10:51:10.09615Z",
+		DomainReportedMalicious: BoolPtr(false),
+		FirstPageURL:            "blog.cloudflare.com/page02",
+		FirstSeenAt:             "2021-08-18T10:51:08Z",
+		Host:                    "blog.cloudflare.com",
+		ID:                      "c9ef84a6bf5e47138c75d95e2f933e8f",
+		LastSeenAt:              "2021-09-02T09:57:54Z",
+		PageURLs:                []string{"blog.cloudflare.com/page1", "blog.cloudflare.com/page2"},
+		URL:                     "https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.6.0/js/bootstrap.min.js",
+		URLContainsCdnCgiPath:   BoolPtr(false),
+	},
+	{
+		AddedAt:                 "2021-10-18T10:51:10.09615Z",
+		DomainReportedMalicious: BoolPtr(false),
+		FirstPageURL:            "blog.cloudflare.com/page03",
+		FirstSeenAt:             "2021-08-18T10:51:08Z",
+		Host:                    "blog.cloudflare.com",
+		ID:                      "c9ef84a6bf5e47138c75d95e2f933e8f",
+		LastSeenAt:              "2021-09-02T09:57:54Z",
+		PageURLs:                []string{"blog.cloudflare.com/page1", "blog.cloudflare.com/page2"},
+		URL:                     "https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.6.0/js/bootstrap.min.js",
+		URLContainsCdnCgiPath:   BoolPtr(false),
+	},
+}
+
+func TestListPageShieldConnections(t *testing.T) {
+	setup()
+	defer teardown()
+
+	mux.HandleFunc("/zones/"+testZoneID+"/page_shield/connections", func(w http.ResponseWriter, r *http.Request) {
+		assert.Equal(t, http.MethodGet, r.Method, "Expected method 'GET', got %s", r.Method)
+		w.Header().Set("content-type", "application/json")
+		response := ListPageShieldConnectionsResponse{
+			Result: mockPageShieldConnections,
+		}
+		err := json.NewEncoder(w).Encode(response)
+		if err != nil {
+			t.Fatal(err)
+		}
+	})
+	result, _, err := client.ListPageShieldConnections(context.Background(), ZoneIdentifier(testZoneID), ListPageShieldConnectionsParams{})
+	assert.NoError(t, err)
+	assert.Equal(t, mockPageShieldConnections, result)
+}
+
+func TestGetPageShieldConnection(t *testing.T) {
+	setup()
+	defer teardown()
+
+	connectionID := "c9ef84a6bf5e47138c75d95e2f933e8f" //nolint
+	mux.HandleFunc(fmt.Sprintf("/zones/"+testZoneID+"/page_shield/connections/%s", connectionID), func(w http.ResponseWriter, r *http.Request) {
+		assert.Equal(t, http.MethodGet, r.Method, "Expected method 'GET', got %s", r.Method)
+		w.Header().Set("content-type", "application/json")
+		response := mockPageShieldConnections[0] // Assuming it's the first mock connection
+		err := json.NewEncoder(w).Encode(response)
+		if err != nil {
+			t.Fatal(err)
+		}
+	})
+	result, err := client.GetPageShieldConnection(context.Background(), ZoneIdentifier(testZoneID), connectionID)
+	assert.NoError(t, err)
+	assert.Equal(t, &mockPageShieldConnections[0], result)
+}
diff --git a/page_shield_policies_test.go b/page_shield_policies_test.go
new file mode 100644
index 000000000..239c4639d
--- /dev/null
+++ b/page_shield_policies_test.go
@@ -0,0 +1,151 @@
+package cloudflare
+
+import (
+	"context"
+	"fmt"
+	"net/http"
+	"testing"
+
+	"github.com/goccy/go-json"
+	"github.com/stretchr/testify/assert"
+)
+
+var mockPageShieldPolicies = []PageShieldPolicy{
+	{
+		Action:      "allow",
+		Description: "Checkout page CSP policy",
+		Enabled:     BoolPtr(true),
+		Expression:  "ends_with(http.request.uri.path, \"/checkout\")",
+		ID:          "c9ef84a6bf5e47138c75d95e2f933e8f",
+		Value:       "script-src 'none';",
+	},
+	{
+		Action:      "allow",
+		Description: "Checkout page CSP policy",
+		Enabled:     BoolPtr(true),
+		Expression:  "ends_with(http.request.uri.path, \"/login\")",
+		ID:          "c9ef84a6bf5e47138c75d95e2f933e82",
+		Value:       "script-src 'none';",
+	},
+	{
+		Action:      "allow",
+		Description: "Checkout page CSP policy",
+		Enabled:     BoolPtr(true),
+		Expression:  "ends_with(http.request.uri.path, \"/logout\")",
+		ID:          "c9ef84a6bf5e47138c75d95e2f933e83",
+		Value:       "script-src 'none';",
+	},
+}
+
+func TestListPageShieldPolicies(t *testing.T) {
+	setup()
+	defer teardown()
+
+	mux.HandleFunc("/zones/"+testZoneID+"/page_shield/policies", func(w http.ResponseWriter, r *http.Request) {
+		assert.Equal(t, http.MethodGet, r.Method, "Expected method 'GET', got %s", r.Method)
+		w.Header().Set("content-type", "application/json")
+		response := ListPageShieldPoliciesResponse{
+			Result: mockPageShieldPolicies,
+		}
+		err := json.NewEncoder(w).Encode(response)
+		if err != nil {
+			t.Fatal(err)
+		}
+	})
+	result, _, err := client.ListPageShieldPolicies(context.Background(), ZoneIdentifier(testZoneID), ListPageShieldPoliciesParams{})
+	assert.NoError(t, err)
+	assert.Equal(t, mockPageShieldPolicies, result)
+}
+
+func TestCreatePageShieldPolicy(t *testing.T) {
+	setup()
+	defer teardown()
+
+	mux.HandleFunc("/zones/"+testZoneID+"/page_shield/policies", func(w http.ResponseWriter, r *http.Request) {
+		assert.Equal(t, http.MethodPost, r.Method, "Expected method 'POST', got %s", r.Method)
+		w.Header().Set("content-type", "application/json")
+		var params PageShieldPolicy
+		err := json.NewDecoder(r.Body).Decode(&params)
+		assert.NoError(t, err)
+		params.ID = "newPolicyID"
+		err = json.NewEncoder(w).Encode(params)
+		if err != nil {
+			t.Fatal(err)
+		}
+	})
+
+	newPolicy := CreatePageShieldPolicyParams{
+		Action:      "block",
+		Description: "New policy",
+		Enabled:     BoolPtr(true),
+		Expression:  "ends_with(http.request.uri.path, \"/new\")",
+		Value:       "script-src 'self';",
+	}
+	result, err := client.CreatePageShieldPolicy(context.Background(), ZoneIdentifier(testZoneID), newPolicy)
+	assert.NoError(t, err)
+	assert.Equal(t, "newPolicyID", result.ID)
+}
+
+func TestDeletePageShieldPolicy(t *testing.T) {
+	setup()
+	defer teardown()
+
+	policyID := "c9ef84a6bf5e47138c75d95e2f933e8f"
+	mux.HandleFunc(fmt.Sprintf("/zones/"+testZoneID+"/page_shield/policies/%s", policyID), func(w http.ResponseWriter, r *http.Request) {
+		assert.Equal(t, http.MethodDelete, r.Method, "Expected method 'DELETE', got %s", r.Method)
+		w.WriteHeader(http.StatusOK) // Assuming successful deletion returns 200 OK
+	})
+	err := client.DeletePageShieldPolicy(context.Background(), ZoneIdentifier(testZoneID), policyID)
+	assert.NoError(t, err)
+}
+
+func TestGetPageShieldPolicy(t *testing.T) {
+	setup()
+	defer teardown()
+
+	policyID := "c9ef84a6bf5e47138c75d95e2f933e8f"
+	mux.HandleFunc(fmt.Sprintf("/zones/"+testZoneID+"/page_shield/policies/%s", policyID), func(w http.ResponseWriter, r *http.Request) {
+		assert.Equal(t, http.MethodGet, r.Method, "Expected method 'GET', got %s", r.Method)
+		w.Header().Set("content-type", "application/json")
+		err := json.NewEncoder(w).Encode(mockPageShieldPolicies[0]) // Assuming the first mock policy
+		if err != nil {
+			t.Fatal(err)
+		}
+	})
+	result, err := client.GetPageShieldPolicy(context.Background(), ZoneIdentifier(testZoneID), policyID)
+	assert.NoError(t, err)
+	assert.Equal(t, &mockPageShieldPolicies[0], result)
+}
+
+func TestUpdatePageShieldPolicy(t *testing.T) {
+	setup()
+	defer teardown()
+
+	policyID := "c9ef84a6bf5e47138c75d95e2f933e8f"
+	mux.HandleFunc(fmt.Sprintf("/zones/"+testZoneID+"/page_shield/policies/%s", policyID), func(w http.ResponseWriter, r *http.Request) {
+		assert.Equal(t, http.MethodPut, r.Method, "Expected method 'PUT', got %s", r.Method)
+		w.Header().Set("content-type", "application/json")
+
+		var params PageShieldPolicy
+		err := json.NewDecoder(r.Body).Decode(&params)
+		assert.NoError(t, err)
+		params.ID = policyID
+		err = json.NewEncoder(w).Encode(params)
+		if err != nil {
+			t.Fatal(err)
+		}
+	})
+
+	updatedPolicy := UpdatePageShieldPolicyParams{
+		ID:          policyID,
+		Action:      "block",
+		Description: "Updated policy",
+		Enabled:     BoolPtr(false),
+		Expression:  "ends_with(http.request.uri.path, \"/updated\")",
+		Value:       "script-src 'self';",
+	}
+	result, err := client.UpdatePageShieldPolicy(context.Background(), ZoneIdentifier(testZoneID), updatedPolicy)
+	assert.NoError(t, err)
+	assert.Equal(t, policyID, result.ID)
+	assert.Equal(t, "Updated policy", result.Description)
+}
diff --git a/page_shield_scripts_test.go b/page_shield_scripts_test.go
new file mode 100644
index 000000000..d6d430dcd
--- /dev/null
+++ b/page_shield_scripts_test.go
@@ -0,0 +1,78 @@
+package cloudflare
+
+import (
+	"context"
+	"fmt"
+	"net/http"
+	"testing"
+
+	"github.com/goccy/go-json"
+	"github.com/stretchr/testify/assert"
+)
+
+var mockPageShieldScripts = []PageShieldScript{
+	{
+		AddedAt:                 "2021-08-18T10:51:10.09615Z",
+		DomainReportedMalicious: BoolPtr(false),
+		FetchedAt:               "2021-09-02T10:17:54Z",
+		FirstPageURL:            "blog.cloudflare.com/page",
+		FirstSeenAt:             "2021-08-18T10:51:08Z",
+		Hash:                    "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+		Host:                    "blog.cloudflare.com",
+		ID:                      "c9ef84a6bf5e47138c75d95e2f933e8f",
+		JSIntegrityScore:        10,
+		LastSeenAt:              "2021-09-02T09:57:54Z",
+		PageURLs:                []string{"blog.cloudflare.com/page1", "blog.cloudflare.com/page2"},
+		URL:                     "https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.6.0/js/bootstrap.min.js",
+		URLContainsCdnCgiPath:   BoolPtr(false),
+	},
+}
+
+var mockVersions = []PageShieldScriptVersion{
+	{
+		FetchedAt:        "2021-09-02T10:17:54Z",
+		Hash:             "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+		JSIntegrityScore: 10,
+	},
+}
+
+func TestListPageShieldScripts(t *testing.T) {
+	setup()
+	defer teardown()
+
+	mux.HandleFunc("/zones/"+testZoneID+"/page_shield/scripts", func(w http.ResponseWriter, r *http.Request) {
+		assert.Equal(t, http.MethodGet, r.Method, "Expected method 'GET', got %s", r.Method)
+		w.Header().Set("content-type", "application/json")
+		response := PageShieldScriptsResponse{Results: mockPageShieldScripts}
+		err := json.NewEncoder(w).Encode(response)
+		if err != nil {
+			t.Fatal(err)
+		}
+	})
+	result, _, err := client.ListPageShieldScripts(context.Background(), ZoneIdentifier(testZoneID), ListPageShieldScriptsParams{})
+	assert.NoError(t, err)
+	assert.Equal(t, mockPageShieldScripts, result)
+}
+
+func TestGetPageShieldScript(t *testing.T) {
+	setup()
+	defer teardown()
+
+	scriptID := "c9ef84a6bf5e47138c75d95e2f933e8f"
+	mux.HandleFunc(fmt.Sprintf("/zones/"+testZoneID+"/page_shield/scripts/%s", scriptID), func(w http.ResponseWriter, r *http.Request) {
+		assert.Equal(t, http.MethodGet, r.Method, "Expected method 'GET', got %s", r.Method)
+		w.Header().Set("content-type", "application/json")
+		response := PageShieldScriptResponse{
+			Result:   mockPageShieldScripts[0],
+			Versions: mockVersions,
+		}
+		err := json.NewEncoder(w).Encode(response)
+		if err != nil {
+			t.Fatal(err)
+		}
+	})
+	result, versions, err := client.GetPageShieldScript(context.Background(), ZoneIdentifier(testZoneID), scriptID)
+	assert.NoError(t, err)
+	assert.Equal(t, &mockPageShieldScripts[0], result)
+	assert.Equal(t, mockVersions, versions)
+}
diff --git a/page_shield_test.go b/page_shield_test.go
new file mode 100644
index 000000000..a41e51e22
--- /dev/null
+++ b/page_shield_test.go
@@ -0,0 +1,77 @@
+package cloudflare
+
+import (
+	"context"
+	"net/http"
+	"testing"
+
+	"github.com/goccy/go-json"
+	"github.com/stretchr/testify/assert"
+)
+
+// Mock PageShieldSettings data.
+var mockPageShieldSettings = PageShieldSettings{
+	PageShield: PageShield{
+		Enabled:                        BoolPtr(true),
+		UseCloudflareReportingEndpoint: BoolPtr(true),
+		UseConnectionURLPath:           BoolPtr(true),
+	},
+	UpdatedAt: "2022-10-12T17:56:52.083582+01:00",
+}
+
+func TestGetPageShieldSettings(t *testing.T) {
+	setup()
+	defer teardown()
+
+	mux.HandleFunc("/zones/"+testZoneID+"/page_shield", func(w http.ResponseWriter, r *http.Request) {
+		assert.Equal(t, http.MethodGet, r.Method, "Expected method 'GET', got %s", r.Method)
+		w.Header().Set("content-type", "application/json")
+		response := PageShieldSettingsResponse{
+			PageShield: mockPageShieldSettings,
+		}
+		err := json.NewEncoder(w).Encode(response)
+		if err != nil {
+			t.Fatal(err)
+		}
+	})
+
+	result, err := client.GetPageShieldSettings(context.Background(), ZoneIdentifier(testZoneID), GetPageShieldSettingsParams{})
+	assert.NoError(t, err)
+	assert.Equal(t, &PageShieldSettingsResponse{PageShield: mockPageShieldSettings}, result)
+}
+
+func TestUpdatePageShieldSettings(t *testing.T) {
+	setup()
+	defer teardown()
+
+	mux.HandleFunc("/zones/"+testZoneID+"/page_shield", func(w http.ResponseWriter, r *http.Request) {
+		assert.Equal(t, http.MethodPut, r.Method, "Expected method 'PUT', got %s", r.Method)
+		w.Header().Set("content-type", "application/json")
+
+		var params PageShield
+		err := json.NewDecoder(r.Body).Decode(&params)
+		assert.NoError(t, err)
+
+		response := PageShieldSettingsResponse{
+			PageShield: PageShieldSettings{
+				PageShield: params,
+				UpdatedAt:  "2022-10-13T10:00:00.000Z",
+			},
+		}
+		err = json.NewEncoder(w).Encode(response)
+		if err != nil {
+			t.Fatal(err)
+		}
+	})
+
+	newSettings := UpdatePageShieldSettingsParams{
+		Enabled:                        BoolPtr(false),
+		UseCloudflareReportingEndpoint: BoolPtr(false),
+		UseConnectionURLPath:           BoolPtr(false),
+	}
+	result, err := client.UpdatePageShieldSettings(context.Background(), ZoneIdentifier(testZoneID), newSettings)
+	assert.NoError(t, err)
+	assert.Equal(t, false, *result.PageShield.Enabled)
+	assert.Equal(t, false, *result.PageShield.UseCloudflareReportingEndpoint)
+	assert.Equal(t, false, *result.PageShield.UseConnectionURLPath)
+}