Repository: cloudflare/cloudflare-go
Pull request #1403 (2023-09-14 20:56:21)

AUTH-5428 added tags to access namespace


## Description

Tags are a new resource to tie to an application.  This change allows for that CRUD on access tags as well as associating them with applications
## Has your change been tested?

Unit tests

## Screenshots (if appropriate):

## 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.
- [x ] My change requires a change to the documentation.
- [x ] 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.
jacobbednarz: a minor question about updating as i don't think it will do anything useful as is but otherwise 👍 . 

Patch:
diff --git a/.changelog/1382.txt b/.changelog/1382.txt
deleted file mode 100644
index 6074328c9..000000000
--- a/.changelog/1382.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-```release-note:bug
-semgrep: Improved IPv4 validation by implementing a new pattern to handle cases where non-IPv4 addresses were previously accepted.
-```
diff --git a/.changelog/1397.txt b/.changelog/1397.txt
deleted file mode 100644
index a3954f447..000000000
--- a/.changelog/1397.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-```release_note:enhancement
-api_shield: Add support for Get/Post/Delete operations in API Shield Endpoint Management
-```
\ No newline at end of file
diff --git a/.changelog/1401.txt b/.changelog/1401.txt
deleted file mode 100644
index b1ef19e84..000000000
--- a/.changelog/1401.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-```release-note:enhancement
-observatory: add support for observatory API
-```
\ No newline at end of file
diff --git a/.changelog/1402.txt b/.changelog/1402.txt
deleted file mode 100644
index 476979377..000000000
--- a/.changelog/1402.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-```release-note:dependency
-deps: bumps codecov/codecov-action from 3 to 4
-```
diff --git a/.changelog/1403.txt b/.changelog/1403.txt
new file mode 100644
index 000000000..92568c880
--- /dev/null
+++ b/.changelog/1403.txt
@@ -0,0 +1,7 @@
+```release-note:enhancement
+access_application: Add support for tags
+```
+
+```release-note:enhancement
+access_tag: Add support for tags
+```
\ No newline at end of file
diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml
index 74804930c..7d3608ff4 100644
--- a/.github/workflows/coverage.yml
+++ b/.github/workflows/coverage.yml
@@ -16,4 +16,4 @@ jobs:
       - name: Run coverage
         run: go test ./... -coverprofile=coverage.txt -covermode=atomic
       - name: Upload coverage to Codecov
-        uses: codecov/codecov-action@v4
+        uses: codecov/codecov-action@v3
diff --git a/.semgrep.yml b/.semgrep.yml
index 7e46eadd0..000f8a23a 100644
--- a/.semgrep.yml
+++ b/.semgrep.yml
@@ -7,7 +7,7 @@ rules:
       include:
         - '*.go'
     patterns:
-      - pattern-regex: '(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)'
+      - pattern-regex: '\d+\.\d+\.\d+\.\d+'
       - pattern-not-regex: '10\.\d+\.\d+.\d+'
       - pattern-not-regex: '192\.168\.\d+.\d+'
       - pattern-not-regex: '192\.0\.2\.\d+'    # 192.0.2.0/24 (TEST-NET-1, rfc5737)
@@ -33,16 +33,3 @@ rules:
         - pattern: |
             errors.Wrap(...)
     severity: WARNING
-  - id: no-use-of-stdlib-json
-    languages:
-      - go
-    message: Favour "github.com/goccy/go-json" instead of "encoding/json" to allow better customisation of marshaling and unmarshaling JSON attributes. See https://github.com/cloudflare/cloudflare-go/pull/1360 for full details.
-    paths:
-      include:
-        - '*.go'
-    patterns:
-      - pattern-regex: '\"encoding\/json\"'
-    fix-regex:
-      regex: \".*\"
-      replacement: '"github.com/goccy/go-json"'
-    severity: WARNING
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ed830ff0b..55d52cb89 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,17 +1,5 @@
 ## 0.78.0 (Unreleased)
 
-ENHANCEMENTS:
-
-* observatory: add support for observatory API ([#1401](https://github.com/cloudflare/cloudflare-go/issues/1401))
-
-BUG FIXES:
-
-* semgrep: Improved IPv4 validation by implementing a new pattern to handle cases where non-IPv4 addresses were previously accepted. ([#1382](https://github.com/cloudflare/cloudflare-go/issues/1382))
-
-DEPENDENCIES:
-
-* deps: bumps codecov/codecov-action from 3 to 4 ([#1402](https://github.com/cloudflare/cloudflare-go/issues/1402))
-
 ## 0.77.0 (September 13th, 2023)
 
 ENHANCEMENTS:
diff --git a/access_application.go b/access_application.go
index 898b3a637..e99bd0004 100644
--- a/access_application.go
+++ b/access_application.go
@@ -53,6 +53,7 @@ type AccessApplication struct {
 	ServiceAuth401Redirect   *bool                          `json:"service_auth_401_redirect,omitempty"`
 	PathCookieAttribute      *bool                          `json:"path_cookie_attribute,omitempty"`
 	CustomPages              []string                       `json:"custom_pages,omitempty"`
+	Tags                     []string                       `json:"tags,omitempty"`
 }
 
 type AccessApplicationGatewayRule struct {
@@ -144,6 +145,7 @@ type CreateAccessApplicationParams struct {
 	SkipInterstitial         *bool                          `json:"skip_interstitial,omitempty"`
 	Type                     AccessApplicationType          `json:"type,omitempty"`
 	CustomPages              []string                       `json:"custom_pages,omitempty"`
+	Tags                     []string                       `json:"tags,omitempty"`
 }
 
 type UpdateAccessApplicationParams struct {
@@ -172,6 +174,7 @@ type UpdateAccessApplicationParams struct {
 	SkipInterstitial         *bool                          `json:"skip_interstitial,omitempty"`
 	Type                     AccessApplicationType          `json:"type,omitempty"`
 	CustomPages              []string                       `json:"custom_pages,omitempty"`
+	Tags                     []string                       `json:"tags,omitempty"`
 }
 
 // ListAccessApplications returns all applications within an account or zone.
diff --git a/access_tag.go b/access_tag.go
new file mode 100644
index 000000000..9bba613d1
--- /dev/null
+++ b/access_tag.go
@@ -0,0 +1,85 @@
+package cloudflare
+
+import (
+	"context"
+	"fmt"
+	"net/http"
+
+	"github.com/goccy/go-json"
+)
+
+type AccessTag struct {
+	Name     string `json:"name,omitempty"`
+	AppCount int    `json:"app_count,omitempty"`
+}
+
+type AccessTagListResponse struct {
+	Response
+	Result     []AccessTag `json:"result"`
+	ResultInfo `json:"result_info"`
+}
+
+type AccessTagResponse struct {
+	Response
+	Result AccessTag `json:"result"`
+}
+
+type ListAccessTagsParams struct{}
+
+type CreateAccessTagParams struct {
+	Name string `json:"name,omitempty"`
+}
+
+func (api *API) ListAccessTags(ctx context.Context, rc *ResourceContainer, params ListAccessTagsParams) ([]AccessTag, error) {
+	uri := buildURI(fmt.Sprintf("/%s/%s/access/tags", rc.Level, rc.Identifier), params)
+	res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
+	if err != nil {
+		return []AccessTag{}, err
+	}
+
+	var TagsResponse AccessTagListResponse
+	err = json.Unmarshal(res, &TagsResponse)
+	if err != nil {
+		return []AccessTag{}, err
+	}
+	return TagsResponse.Result, nil
+}
+
+func (api *API) GetAccessTag(ctx context.Context, rc *ResourceContainer, tagName string) (AccessTag, error) {
+	uri := fmt.Sprintf("/%s/%s/access/tags/%s", rc.Level, rc.Identifier, tagName)
+	res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
+	if err != nil {
+		return AccessTag{}, err
+	}
+
+	var TagResponse AccessTagResponse
+	err = json.Unmarshal(res, &TagResponse)
+	if err != nil {
+		return AccessTag{}, err
+	}
+	return TagResponse.Result, nil
+}
+
+func (api *API) CreateAccessTag(ctx context.Context, rc *ResourceContainer, params CreateAccessTagParams) (AccessTag, error) {
+	uri := fmt.Sprintf("/%s/%s/access/tags", rc.Level, rc.Identifier)
+	res, err := api.makeRequestContext(ctx, http.MethodPost, uri, params)
+	if err != nil {
+		return AccessTag{}, err
+	}
+
+	var TagResponse AccessTagResponse
+	err = json.Unmarshal(res, &TagResponse)
+	if err != nil {
+		return AccessTag{}, err
+	}
+	return TagResponse.Result, nil
+}
+
+func (api *API) DeleteAccessTag(ctx context.Context, rc *ResourceContainer, tagName string) error {
+	uri := fmt.Sprintf("/%s/%s/access/tags/%s", rc.Level, rc.Identifier, tagName)
+	_, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil)
+	if err != nil {
+		return err
+	}
+	return nil
+}
diff --git a/api_shield_operations.go b/api_shield_operations.go
deleted file mode 100644
index 1d89a1754..000000000
--- a/api_shield_operations.go
+++ /dev/null
@@ -1,185 +0,0 @@
-package cloudflare
-
-import (
-	"context"
-	"fmt"
-	"net/http"
-	"time"
-
-	"github.com/goccy/go-json"
-)
-
-// APIShieldOperation represents an operation stored in API Shield Endpoint Management.
-type APIShieldOperation struct {
-	APIShieldBasicOperation
-	ID          string         `json:"operation_id"`
-	LastUpdated *time.Time     `json:"last_updated"`
-	Features    map[string]any `json:"features,omitempty"`
-}
-
-// GetAPIShieldOperationParams represents the parameters to pass when retrieving an operation.
-//
-// API documentation: https://developers.cloudflare.com/api/operations/api-shield-endpoint-management-retrieve-information-about-an-operation
-type GetAPIShieldOperationParams struct {
-	// The Operation ID to retrieve
-	OperationID string `url:"-"`
-	// Features represents a set of features to return in `features` object when
-	// performing making read requests against an Operation or listing operations.
-	Features []string `url:"feature,omitempty"`
-}
-
-// CreateAPIShieldOperationsParams represents the parameters to pass when adding one or more operations.
-//
-// API documentation: https://developers.cloudflare.com/api/operations/api-shield-endpoint-management-add-operations-to-a-zone
-type CreateAPIShieldOperationsParams struct {
-	// Operations are a slice of operations to be created in API Shield Endpoint Management
-	Operations []APIShieldBasicOperation `url:"-"`
-}
-
-// APIShieldBasicOperation should be used when creating an operation in API Shield Endpoint Management.
-type APIShieldBasicOperation struct {
-	Method   string `json:"method"`
-	Host     string `json:"host"`
-	Endpoint string `json:"endpoint"`
-}
-
-// DeleteAPIShieldOperationParams represents the parameters to pass to delete an operation.
-//
-// API documentation: https://developers.cloudflare.com/api/operations/api-shield-endpoint-management-delete-an-operation
-type DeleteAPIShieldOperationParams struct {
-	// OperationID is the operation to be deleted
-	OperationID string `url:"-"`
-}
-
-// ListAPIShieldOperationsParams represents the parameters to pass when retrieving operations
-//
-// API documentation: https://developers.cloudflare.com/api/operations/api-shield-endpoint-management-retrieve-information-about-all-operations-on-a-zone
-type ListAPIShieldOperationsParams struct {
-	// Features represents a set of features to return in `features` object when
-	// performing making read requests against an Operation or listing operations.
-	Features []string `url:"feature,omitempty"`
-	// Direction to order results.
-	Direction string `url:"direction,omitempty"`
-	// OrderBy when requesting a feature, the feature keys are available for ordering as well, e.g., thresholds.suggested_threshold.
-	OrderBy string `url:"order,omitempty"`
-	// Filters to only return operations that match filtering criteria, see APIShieldGetOperationsFilters
-	APIShieldListOperationsFilters
-	// Pagination options to apply to the request.
-	PaginationOptions
-}
-
-// APIShieldListOperationsFilters represents the filtering query parameters to set when retrieving operations
-//
-// API documentation: https://developers.cloudflare.com/api/operations/api-shield-endpoint-management-retrieve-information-about-all-operations-on-a-zone
-type APIShieldListOperationsFilters struct {
-	// Hosts filters results to only include the specified hosts.
-	Hosts []string `url:"host,omitempty"`
-	// Methods filters results to only include the specified methods.
-	Methods []string `url:"method,omitempty"`
-	// Endpoint filter results to only include endpoints containing this pattern.
-	Endpoint string `url:"endpoint,omitempty"`
-}
-
-// APIShieldGetOperationResponse represents the response from the api_gateway/operations/{id} endpoint.
-type APIShieldGetOperationResponse struct {
-	Result APIShieldOperation `json:"result"`
-	Response
-}
-
-// APIShieldGetOperationsResponse represents the response from the api_gateway/operations endpoint.
-type APIShieldGetOperationsResponse struct {
-	Result     []APIShieldOperation `json:"result"`
-	ResultInfo `json:"result_info"`
-	Response
-}
-
-// APIShieldDeleteOperationResponse represents the response from the api_gateway/operations/{id} endpoint (DELETE).
-type APIShieldDeleteOperationResponse struct {
-	Result interface{} `json:"result"`
-	Response
-}
-
-// GetAPIShieldOperation returns information about an operation
-//
-// API documentation https://developers.cloudflare.com/api/operations/api-shield-endpoint-management-retrieve-information-about-an-operation
-func (api *API) GetAPIShieldOperation(ctx context.Context, rc *ResourceContainer, params GetAPIShieldOperationParams) (*APIShieldOperation, error) {
-	path := fmt.Sprintf("/zones/%s/api_gateway/operations/%s", rc.Identifier, params.OperationID)
-
-	uri := buildURI(path, params)
-
-	res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
-	if err != nil {
-		return nil, err
-	}
-
-	var asResponse APIShieldGetOperationResponse
-	err = json.Unmarshal(res, &asResponse)
-	if err != nil {
-		return nil, fmt.Errorf("%s: %w", errUnmarshalError, err)
-	}
-
-	return &asResponse.Result, nil
-}
-
-// ListAPIShieldOperations retrieve information about all operations on a zone
-//
-// API documentation https://developers.cloudflare.com/api/operations/api-shield-endpoint-management-retrieve-information-about-all-operations-on-a-zone
-func (api *API) ListAPIShieldOperations(ctx context.Context, rc *ResourceContainer, params ListAPIShieldOperationsParams) ([]APIShieldOperation, ResultInfo, error) {
-	path := fmt.Sprintf("/zones/%s/api_gateway/operations", rc.Identifier)
-
-	uri := buildURI(path, params)
-
-	res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
-	if err != nil {
-		return nil, ResultInfo{}, err
-	}
-
-	var asResponse APIShieldGetOperationsResponse
-	err = json.Unmarshal(res, &asResponse)
-	if err != nil {
-		return nil, ResultInfo{}, fmt.Errorf("%s: %w", errUnmarshalError, err)
-	}
-
-	return asResponse.Result, asResponse.ResultInfo, nil
-}
-
-// CreateAPIShieldOperations add one or more operations to a zone.
-//
-// API documentation https://developers.cloudflare.com/api/operations/api-shield-endpoint-management-add-operations-to-a-zone
-func (api *API) CreateAPIShieldOperations(ctx context.Context, rc *ResourceContainer, params CreateAPIShieldOperationsParams) ([]APIShieldOperation, error) {
-	uri := fmt.Sprintf("/zones/%s/api_gateway/operations", rc.Identifier)
-
-	res, err := api.makeRequestContext(ctx, http.MethodPost, uri, params.Operations)
-	if err != nil {
-		return nil, err
-	}
-
-	// Result should be all the operations added to the zone, similar to doing GetAPIShieldOperations
-	var asResponse APIShieldGetOperationsResponse
-	err = json.Unmarshal(res, &asResponse)
-	if err != nil {
-		return nil, fmt.Errorf("%s: %w", errUnmarshalError, err)
-	}
-
-	return asResponse.Result, nil
-}
-
-// DeleteAPIShieldOperation deletes a single operation
-//
-// API documentation https://developers.cloudflare.com/api/operations/api-shield-endpoint-management-delete-an-operation
-func (api *API) DeleteAPIShieldOperation(ctx context.Context, rc *ResourceContainer, params DeleteAPIShieldOperationParams) error {
-	uri := fmt.Sprintf("/zones/%s/api_gateway/operations/%s", rc.Identifier, params.OperationID)
-
-	res, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil)
-	if err != nil {
-		return err
-	}
-
-	var asResponse APIShieldDeleteOperationResponse
-	err = json.Unmarshal(res, &asResponse)
-	if err != nil {
-		return fmt.Errorf("%s: %w", errUnmarshalError, err)
-	}
-
-	return nil
-}
diff --git a/bot_management.go b/bot_management.go
index f77968f45..f44d39ffb 100644
--- a/bot_management.go
+++ b/bot_management.go
@@ -2,10 +2,9 @@ package cloudflare
 
 import (
 	"context"
+	"encoding/json"
 	"fmt"
 	"net/http"
-
-	"github.com/goccy/go-json"
 )
 
 // BotManagement represents the bots config for a zone.
diff --git a/observatory.go b/observatory.go
deleted file mode 100644
index 488640fce..000000000
--- a/observatory.go
+++ /dev/null
@@ -1,388 +0,0 @@
-package cloudflare
-
-import (
-	"context"
-	"errors"
-	"fmt"
-	"net/http"
-	"net/url"
-	"time"
-
-	"github.com/goccy/go-json"
-)
-
-var (
-	ErrMissingObservatoryUrl    = errors.New("missing required page url")
-	ErrMissingObservatoryTestID = errors.New("missing required test id")
-)
-
-// ObservatoryPage describes all the tests for a web page.
-type ObservatoryPage struct {
-	URL               string                `json:"url"`
-	Region            labeledRegion         `json:"region"`
-	ScheduleFrequency string                `json:"scheduleFrequency"`
-	Tests             []ObservatoryPageTest `json:"tests"`
-}
-
-// ObservatoryPageTest describes a single test for a web page.
-type ObservatoryPageTest struct {
-	ID                string                      `json:"id"`
-	Date              *time.Time                  `json:"date"`
-	URL               string                      `json:"url"`
-	Region            labeledRegion               `json:"region"`
-	ScheduleFrequency *string                     `json:"scheduleFrequency"`
-	MobileReport      ObservatoryLighthouseReport `json:"mobileReport"`
-	DesktopReport     ObservatoryLighthouseReport `json:"desktopReport"`
-}
-
-// labeledRegion describes a region the test was run in.
-type labeledRegion struct {
-	Value string `json:"value"`
-	Label string `json:"label"`
-}
-
-// ObservatorySchedule describe a test schedule.
-type ObservatorySchedule struct {
-	URL       string `json:"url"`
-	Region    string `json:"region"`
-	Frequency string `json:"frequency"`
-}
-
-// ObservatoryLighthouseReport describes the web vital metrics result.
-type ObservatoryLighthouseReport struct {
-	PerformanceScore int    `json:"performanceScore"`
-	State            string `json:"state"`
-	DeviceType       string `json:"deviceType"`
-	// TTFB is time to first byte
-	TTFB int `json:"ttfb"`
-	// FCP is first contentful paint
-	FCP int `json:"fcp"`
-	// LCP is largest contentful pain
-	LCP int `json:"lcp"`
-	// TTI is time to interactive
-	TTI int `json:"tti"`
-	// TBT is total blocking time
-	TBT int `json:"tbt"`
-	// SI is speed index
-	SI int `json:"si"`
-	// CLS is cumulative layout shift
-	CLS   float64          `json:"cls"`
-	Error *lighthouseError `json:"error,omitempty"`
-}
-
-// lighthouseError describes the test error.
-type lighthouseError struct {
-	Code              string `json:"code"`
-	Detail            string `json:"detail"`
-	FinalDisplayedURL string `json:"finalDisplayedUrl"`
-}
-
-// ObservatoryPageTrend describes the web vital metrics trend.
-type ObservatoryPageTrend struct {
-	PerformanceScore []*int     `json:"performanceScore"`
-	TTFB             []*int     `json:"ttfb"`
-	FCP              []*int     `json:"fcp"`
-	LCP              []*int     `json:"lcp"`
-	TTI              []*int     `json:"tti"`
-	TBT              []*int     `json:"tbt"`
-	SI               []*int     `json:"si"`
-	CLS              []*float64 `json:"cls"`
-}
-
-type ListObservatoryPagesParams struct {
-}
-
-// ObservatoryPagesResponse is the API response, containing a list of ObservatoryPage.
-type ObservatoryPagesResponse struct {
-	Response
-	Result []ObservatoryPage `json:"result"`
-}
-
-// ListObservatoryPages returns a list of pages which have been tested.
-//
-// API reference: https://api.cloudflare.com/#speed-list-pages
-func (api *API) ListObservatoryPages(ctx context.Context, rc *ResourceContainer, params ListObservatoryPagesParams) ([]ObservatoryPage, error) {
-	uri := fmt.Sprintf("/zones/%s/speed_api/pages", rc.Identifier)
-	res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
-	if err != nil {
-		return nil, err
-	}
-	var r ObservatoryPagesResponse
-	err = json.Unmarshal(res, &r)
-	if err != nil {
-		return nil, fmt.Errorf("%s: %w", errUnmarshalError, err)
-	}
-	return r.Result, nil
-}
-
-type GetObservatoryPageTrendParams struct {
-	URL        string     `url:"-"`
-	Region     string     `url:"region"`
-	DeviceType string     `url:"deviceType"`
-	Start      *time.Time `url:"start"`
-	End        *time.Time `url:"end,omitempty"`
-	Timezone   string     `url:"tz"`
-	Metrics    []string   `url:"metrics"`
-}
-
-type ObservatoryPageTrendResponse struct {
-	Response
-	Result ObservatoryPageTrend `json:"result"`
-}
-
-// GetObservatoryPageTrend returns a the trend of web vital metrics for a page in a specific region.
-//
-// API reference: https://api.cloudflare.com/#speed-list-page-trend
-func (api *API) GetObservatoryPageTrend(ctx context.Context, rc *ResourceContainer, params GetObservatoryPageTrendParams) (*ObservatoryPageTrend, error) {
-	if params.URL == "" {
-		return nil, ErrMissingObservatoryUrl
-	}
-	uri := buildURI(fmt.Sprintf("/zones/%s/speed_api/pages/%s/trend", rc.Identifier, url.PathEscape(params.URL)), params)
-	res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
-	if err != nil {
-		return nil, err
-	}
-	var r ObservatoryPageTrendResponse
-	err = json.Unmarshal(res, &r)
-	if err != nil {
-		return nil, fmt.Errorf("%s: %w", errUnmarshalError, err)
-	}
-	return &r.Result, nil
-}
-
-var listObservatoryPageTestDefaultPageSize = 20
-
-type ListObservatoryPageTestParams struct {
-	URL    string `url:"-"`
-	Region string `url:"region"`
-	ResultInfo
-}
-
-type ObservatoryPageTestsResponse struct {
-	Response
-	Result     []ObservatoryPageTest `json:"result"`
-	ResultInfo `json:"result_info"`
-}
-
-// ListObservatoryPageTests returns a list of tests for a page in a specific region.
-//
-// API reference: https://api.cloudflare.com/#speed-list-test-history
-func (api *API) ListObservatoryPageTests(ctx context.Context, rc *ResourceContainer, params ListObservatoryPageTestParams) ([]ObservatoryPageTest, *ResultInfo, error) {
-	if params.URL == "" {
-		return nil, nil, ErrMissingObservatoryUrl
-	}
-	autoPaginate := true
-	if params.PerPage >= 1 || params.Page >= 1 {
-		autoPaginate = false
-	}
-	if params.PerPage < 1 {
-		params.PerPage = listObservatoryPageTestDefaultPageSize
-	}
-	if params.Page < 1 {
-		params.Page = 1
-	}
-	var tests []ObservatoryPageTest
-	var lastResultInfo ResultInfo
-	for {
-		uri := buildURI(fmt.Sprintf("/zones/%s/speed_api/pages/%s/tests", rc.Identifier, url.PathEscape(params.URL)), params)
-		res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
-		if err != nil {
-			return nil, nil, err
-		}
-		var r ObservatoryPageTestsResponse
-		err = json.Unmarshal(res, &r)
-		if err != nil {
-			return nil, nil, fmt.Errorf("%s: %w", errUnmarshalError, err)
-		}
-		tests = append(tests, r.Result...)
-		lastResultInfo = r.ResultInfo.Next()
-		if params.ResultInfo.Done() || !autoPaginate {
-			break
-		}
-	}
-	return tests, &lastResultInfo, nil
-}
-
-type CreateObservatoryPageTestParams struct {
-	URL      string
-	Settings CreateObservatoryPageTestSettings
-}
-type CreateObservatoryPageTestSettings struct {
-	Region string `json:"region"`
-}
-
-type ObservatoryPageTestResponse struct {
-	Response
-	Result ObservatoryPageTest `json:"result"`
-}
-
-// CreateObservatoryPageTest starts a test for a page in a specific region.
-//
-// API reference: https://api.cloudflare.com/#speed-create-test
-func (api *API) CreateObservatoryPageTest(ctx context.Context, rc *ResourceContainer, params CreateObservatoryPageTestParams) (*ObservatoryPageTest, error) {
-	if params.URL == "" {
-		return nil, ErrMissingObservatoryUrl
-	}
-	uri := fmt.Sprintf("/zones/%s/speed_api/pages/%s/tests", rc.Identifier, url.PathEscape(params.URL))
-	res, err := api.makeRequestContext(ctx, http.MethodPost, uri, params.Settings)
-	if err != nil {
-		return nil, err
-	}
-	var r ObservatoryPageTestResponse
-	err = json.Unmarshal(res, &r)
-	if err != nil {
-		return nil, fmt.Errorf("%s: %w", errUnmarshalError, err)
-	}
-	return &r.Result, nil
-}
-
-type DeleteObservatoryPageTestsParams struct {
-	URL    string `url:"-"`
-	Region string `url:"region"`
-}
-
-type ObservatoryCountResponse struct {
-	Response
-	Result struct {
-		Count int `json:"count"`
-	} `json:"result"`
-}
-
-// DeleteObservatoryPageTests deletes all tests for a page in a specific region.
-//
-// API reference: https://api.cloudflare.com/#speed-delete-tests
-func (api *API) DeleteObservatoryPageTests(ctx context.Context, rc *ResourceContainer, params DeleteObservatoryPageTestsParams) (*int, error) {
-	if params.URL == "" {
-		return nil, ErrMissingObservatoryUrl
-	}
-	uri := buildURI(fmt.Sprintf("/zones/%s/speed_api/pages/%s/tests", rc.Identifier, url.PathEscape(params.URL)), params)
-	res, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil)
-	if err != nil {
-		return nil, err
-	}
-	var r ObservatoryCountResponse
-	err = json.Unmarshal(res, &r)
-	if err != nil {
-		return nil, fmt.Errorf("%s: %w", errUnmarshalError, err)
-	}
-	return &r.Result.Count, nil
-}
-
-type GetObservatoryPageTestParams struct {
-	URL    string
-	TestID string
-}
-
-// GetObservatoryPageTest returns a specific test for a page.
-//
-// API reference: https://api.cloudflare.com/#speed-get-test
-func (api *API) GetObservatoryPageTest(ctx context.Context, rc *ResourceContainer, params GetObservatoryPageTestParams) (*ObservatoryPageTest, error) {
-	if params.URL == "" {
-		return nil, ErrMissingObservatoryUrl
-	}
-	if params.TestID == "" {
-		return nil, ErrMissingObservatoryTestID
-	}
-	uri := fmt.Sprintf("/zones/%s/speed_api/pages/%s/tests/%s", rc.Identifier, url.PathEscape(params.URL), params.TestID)
-	res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
-	if err != nil {
-		return nil, err
-	}
-	var r ObservatoryPageTestResponse
-	err = json.Unmarshal(res, &r)
-	if err != nil {
-		return nil, fmt.Errorf("%s: %w", errUnmarshalError, err)
-	}
-	return &r.Result, nil
-}
-
-type CreateObservatoryScheduledPageTestParams struct {
-	URL       string `url:"-" json:"-"`
-	Region    string `url:"region" json:"region"`
-	Frequency string `url:"frequency" json:"-"`
-}
-
-type ObservatoryScheduledPageTest struct {
-	Schedule ObservatorySchedule `json:"schedule"`
-	Test     ObservatoryPageTest `json:"test"`
-}
-
-type CreateObservatoryScheduledPageTestResponse struct {
-	Response
-	Result ObservatoryScheduledPageTest `json:"result"`
-}
-
-// CreateObservatoryScheduledPageTest creates a scheduled test for a page in a specific region.
-//
-// API reference: https://api.cloudflare.com/#speed-create-scheduled-test
-func (api *API) CreateObservatoryScheduledPageTest(ctx context.Context, rc *ResourceContainer, params CreateObservatoryScheduledPageTestParams) (*ObservatoryScheduledPageTest, error) {
-	if params.URL == "" {
-		return nil, ErrMissingObservatoryUrl
-	}
-	uri := buildURI(fmt.Sprintf("/zones/%s/speed_api/schedule/%s", rc.Identifier, url.PathEscape(params.URL)), params)
-	res, err := api.makeRequestContext(ctx, http.MethodPost, uri, params)
-	if err != nil {
-		return nil, err
-	}
-	var r CreateObservatoryScheduledPageTestResponse
-	err = json.Unmarshal(res, &r)
-	if err != nil {
-		return nil, fmt.Errorf("%s: %w", errUnmarshalError, err)
-	}
-	return &r.Result, nil
-}
-
-type GetObservatoryScheduledPageTestParams struct {
-	URL    string `url:"-"`
-	Region string `url:"region"`
-}
-
-type ObservatoryScheduleResponse struct {
-	Response
-	Result ObservatorySchedule `json:"result"`
-}
-
-// GetObservatoryScheduledPageTest returns the test schedule for a page in a specific region.
-//
-// API reference: https://api.cloudflare.com/#speed-get-scheduled-test
-func (api *API) GetObservatoryScheduledPageTest(ctx context.Context, rc *ResourceContainer, params GetObservatoryScheduledPageTestParams) (*ObservatorySchedule, error) {
-	if params.URL == "" {
-		return nil, ErrMissingObservatoryUrl
-	}
-	uri := buildURI(fmt.Sprintf("/zones/%s/speed_api/schedule/%s", rc.Identifier, url.PathEscape(params.URL)), params)
-	res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
-	if err != nil {
-		return nil, err
-	}
-	var r ObservatoryScheduleResponse
-	err = json.Unmarshal(res, &r)
-	if err != nil {
-		return nil, fmt.Errorf("%s: %w", errUnmarshalError, err)
-	}
-	return &r.Result, nil
-}
-
-type DeleteObservatoryScheduledPageTestParams struct {
-	URL    string `url:"-"`
-	Region string `url:"region"`
-}
-
-// DeleteObservatoryScheduledPageTest deletes the test schedule for a page in a specific region.
-//
-// API reference: https://api.cloudflare.com/#speed-delete-scheduled-test
-func (api *API) DeleteObservatoryScheduledPageTest(ctx context.Context, rc *ResourceContainer, params DeleteObservatoryScheduledPageTestParams) (*int, error) {
-	if params.URL == "" {
-		return nil, ErrMissingObservatoryUrl
-	}
-	uri := buildURI(fmt.Sprintf("/zones/%s/speed_api/schedule/%s", rc.Identifier, url.PathEscape(params.URL)), params)
-	res, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil)
-	if err != nil {
-		return nil, err
-	}
-	var r ObservatoryCountResponse
-	err = json.Unmarshal(res, &r)
-	if err != nil {
-		return nil, fmt.Errorf("%s: %w", errUnmarshalError, err)
-	}
-	return &r.Result.Count, nil
-}
diff --git a/per_hostname_tls_settings.go b/per_hostname_tls_settings.go
index 74b3ce73f..26beec884 100644
--- a/per_hostname_tls_settings.go
+++ b/per_hostname_tls_settings.go
@@ -2,12 +2,11 @@ package cloudflare
 
 import (
 	"context"
+	"encoding/json"
 	"errors"
 	"fmt"
 	"net/http"
 	"time"
-
-	"github.com/goccy/go-json"
 )
 
 // HostnameTLSSetting represents the metadata for a user-created tls setting.
diff --git a/web_analytics.go b/web_analytics.go
index 9f3041f1a..40e6d1bde 100644
--- a/web_analytics.go
+++ b/web_analytics.go
@@ -2,12 +2,11 @@ package cloudflare
 
 import (
 	"context"
+	"encoding/json"
 	"errors"
 	"fmt"
 	"net/http"
 	"time"
-
-	"github.com/goccy/go-json"
 )
 
 var (


Test patch:
diff --git a/access_application_test.go b/access_application_test.go
index d3423837c..5543966a5 100644
--- a/access_application_test.go
+++ b/access_application_test.go
@@ -48,7 +48,8 @@ func TestAccessApplications(t *testing.T) {
 					"app_launcher_visible": true,
 					"service_auth_401_redirect": true,
 					"path_cookie_attribute": true,
-					"custom_pages": ["480f4f69-1a28-4fdd-9240-1ed29f0ac1dc"]
+					"custom_pages": ["480f4f69-1a28-4fdd-9240-1ed29f0ac1dc"],
+					"tags": ["engineers"]
 				}
 			],
 			"result_info": {
@@ -86,6 +87,7 @@ func TestAccessApplications(t *testing.T) {
 		SkipInterstitial:         BoolPtr(true),
 		PathCookieAttribute:      BoolPtr(true),
 		CustomPages:              []string{"480f4f69-1a28-4fdd-9240-1ed29f0ac1dc"},
+		Tags:                     []string{"engineers"},
 		CustomNonIdentityDenyURL: "https://blocked.com",
 	}}
 
@@ -218,7 +220,8 @@ func TestCreateAccessApplications(t *testing.T) {
 				"logo_url": "https://www.example.com/example.png",
 				"skip_interstitial": true,
 				"app_launcher_visible": true,
-				"service_auth_401_redirect": true
+				"service_auth_401_redirect": true,
+				"tags": ["engineers"]
 			}
 		}
 		`)
@@ -246,6 +249,7 @@ func TestCreateAccessApplications(t *testing.T) {
 		CreatedAt:                &createdAt,
 		UpdatedAt:                &updatedAt,
 		CustomNonIdentityDenyURL: "https://blocked.com",
+		Tags:                     []string{"engineers"},
 	}
 
 	mux.HandleFunc("/accounts/"+testAccountID+"/access/apps", handler)
@@ -303,7 +307,8 @@ func TestUpdateAccessApplication(t *testing.T) {
 				"logo_url": "https://www.example.com/example.png",
 				"skip_interstitial": true,
 				"app_launcher_visible": true,
-				"service_auth_401_redirect": true
+				"service_auth_401_redirect": true,
+				"tags": ["engineers"]
 			}
 		}
 		`)
@@ -326,6 +331,7 @@ func TestUpdateAccessApplication(t *testing.T) {
 		CustomDenyURL:            "https://www.example.com",
 		LogoURL:                  "https://www.example.com/example.png",
 		CustomNonIdentityDenyURL: "https://blocked.com",
+		Tags:                     []string{"engineers"},
 		SkipInterstitial:         BoolPtr(true),
 		CreatedAt:                &createdAt,
 		UpdatedAt:                &updatedAt,
@@ -349,6 +355,7 @@ func TestUpdateAccessApplication(t *testing.T) {
 		LogoURL:                  "https://www.example.com/example.png",
 		SkipInterstitial:         BoolPtr(true),
 		CustomNonIdentityDenyURL: "https://blocked.com",
+		Tags:                     []string{"engineers"},
 	}
 
 	mux.HandleFunc("/accounts/"+testAccountID+"/access/apps/480f4f69-1a28-4fdd-9240-1ed29f0ac1db", handler)
@@ -634,6 +641,7 @@ func TestCreateSaasAccessApplications(t *testing.T) {
 				"app_launcher_visible": true,
 				"service_auth_401_redirect": true,
 				"custom_non_identity_deny_url": "https://blocked.com",
+				"tags": ["engineers"],
 				"saas_app": {
 					"consumer_service_url": "https://saas.example.com",
 					"sp_entity_id": "dash.example.com",
@@ -716,6 +724,7 @@ func TestCreateSaasAccessApplications(t *testing.T) {
 		CreatedAt:                &createdAt,
 		UpdatedAt:                &updatedAt,
 		CustomNonIdentityDenyURL: "https://blocked.com",
+		Tags:                     []string{"engineers"},
 	}
 
 	mux.HandleFunc("/accounts/"+testAccountID+"/access/apps", handler)
diff --git a/access_tag_test.go b/access_tag_test.go
new file mode 100644
index 000000000..612a9e6c5
--- /dev/null
+++ b/access_tag_test.go
@@ -0,0 +1,136 @@
+package cloudflare
+
+import (
+	"context"
+	"fmt"
+	"net/http"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestAccessTags(t *testing.T) {
+	setup()
+	defer teardown()
+
+	handler := func(w http.ResponseWriter, r *http.Request) {
+		assert.Equal(t, r.Method, http.MethodGet, "HTTP method")
+		w.Header().Set("content-type", "application/json")
+		fmt.Fprintf(w, `{
+			"success": true,
+			"errors": [],
+			"messages": [],
+			"result": [
+				{
+					"name": "engineers",
+					"app_count": 0
+				}
+			],
+			"result_info": {
+				"page": 1,
+				"per_page": 20,
+				"count": 1,
+				"total_count": 1
+			}
+		}`)
+	}
+
+	want := []AccessTag{
+		{
+			Name:     "engineers",
+			AppCount: 0,
+		},
+	}
+	mux.HandleFunc("/accounts/"+testAccountID+"/access/tags", handler)
+	actual, err := client.ListAccessTags(context.Background(), AccountIdentifier(testAccountID), ListAccessTagsParams{})
+
+	if assert.NoError(t, err) {
+		assert.Equal(t, want, actual)
+	}
+}
+
+func TestAccessTag(t *testing.T) {
+	setup()
+	defer teardown()
+
+	handler := func(w http.ResponseWriter, r *http.Request) {
+		assert.Equal(t, r.Method, http.MethodGet, "HTTP method")
+		w.Header().Set("Content-Type", "application/json")
+		fmt.Fprintf(w, `{
+			"success": true,
+			"errors": [],
+			"messages": [],
+			"result": {
+				"name": "engineers",
+				"app_count": 0
+			}
+		}`)
+	}
+
+	want := AccessTag{
+		Name:     "engineers",
+		AppCount: 0,
+	}
+	mux.HandleFunc("/accounts/"+testAccountID+"/access/tags/engineers", handler)
+	actual, err := client.GetAccessTag(context.Background(), AccountIdentifier(testAccountID), "engineers")
+
+	if assert.NoError(t, err) {
+		assert.Equal(t, want, actual)
+	}
+}
+
+func TestCreateAccessTag(t *testing.T) {
+	setup()
+	defer teardown()
+
+	handler := func(w http.ResponseWriter, r *http.Request) {
+		assert.Equal(t, r.Method, http.MethodPost, "HTTP method")
+		w.Header().Set("Content-Type", "application/json")
+		fmt.Fprintf(w, `{
+			"success": true,
+			"errors": [],
+			"messages": [],
+			"result": {
+				"name": "sales",
+				"app_count": 0
+			}
+		}`)
+	}
+
+	Tag := AccessTag{
+		Name:     "sales",
+		AppCount: 0,
+	}
+
+	mux.HandleFunc("/accounts/"+testAccountID+"/access/tags", handler)
+	actual, err := client.CreateAccessTag(context.Background(), AccountIdentifier(testAccountID), CreateAccessTagParams{
+		Name: "sales",
+	})
+
+	if assert.NoError(t, err) {
+		assert.Equal(t, Tag, actual)
+	}
+}
+
+func TestDeleteAccessTag(t *testing.T) {
+	setup()
+	defer teardown()
+
+	handler := func(w http.ResponseWriter, r *http.Request) {
+		assert.Equal(t, r.Method, http.MethodDelete, "HTTP method")
+		w.Header().Set("Content-Type", "application/json")
+		fmt.Fprintf(w, `{
+			"success": true,
+			"errors": [],
+			"messages": [],
+			result: {
+				"id": "engineers"
+			}
+		}`)
+	}
+
+	mux.HandleFunc("/accounts/"+testAccountID+"/access/tags/engineers", handler)
+	err := client.DeleteAccessTag(context.Background(), AccountIdentifier(testAccountID), "engineers")
+
+	assert.NoError(t, err)
+}
diff --git a/api_shield_operations_test.go b/api_shield_operations_test.go
deleted file mode 100644
index fdcc25cc8..000000000
--- a/api_shield_operations_test.go
+++ /dev/null
@@ -1,491 +0,0 @@
-package cloudflare
-
-import (
-	"context"
-	"fmt"
-	"io"
-	"net/http"
-	"net/url"
-	"testing"
-	"time"
-
-	"github.com/stretchr/testify/assert"
-	"github.com/stretchr/testify/require"
-)
-
-const testAPIShieldOperationId = "9def2cb0-3ed0-4737-92ca-f09efa4718fd"
-
-func TestGetAPIShieldOperation(t *testing.T) {
-	setup()
-	t.Cleanup(teardown)
-
-	endpoint := fmt.Sprintf("/zones/%s/api_gateway/operations/%s", testZoneID, testAPIShieldOperationId)
-	response := `{
-		"success" : true,
-		"errors": [],
-		"messages": [],
-		"result": {
-			"operation_id": "9def2cb0-3ed0-4737-92ca-f09efa4718fd",
-			"method": "POST",
-			"host": "api.cloudflare.com",
-			"endpoint": "/client/v4/zones",
-			"last_updated": "2023-03-02T15:46:06.000000Z"
-		}
-	}`
-
-	handler := func(w http.ResponseWriter, r *http.Request) {
-		require.Equal(t, http.MethodGet, r.Method, "Expected method 'GET', got %s", r.Method)
-		require.Empty(t, r.URL.Query())
-		w.Header().Set("content-type", "application/json")
-		fmt.Fprint(w, response)
-	}
-
-	mux.HandleFunc(endpoint, handler)
-
-	actual, err := client.GetAPIShieldOperation(
-		context.Background(),
-		ZoneIdentifier(testZoneID),
-		GetAPIShieldOperationParams{
-			OperationID: testAPIShieldOperationId,
-		},
-	)
-
-	time := time.Date(2023, time.March, 2, 15, 46, 6, 0, time.UTC)
-	expected := &APIShieldOperation{
-		APIShieldBasicOperation: APIShieldBasicOperation{
-			Method:   "POST",
-			Host:     "api.cloudflare.com",
-			Endpoint: "/client/v4/zones",
-		},
-		ID:          testAPIShieldOperationId,
-		LastUpdated: &time,
-		Features:    nil,
-	}
-
-	if assert.NoError(t, err) {
-		assert.Equal(t, expected, actual)
-	}
-}
-
-func TestGetAPIShieldOperationWithParams(t *testing.T) {
-	endpoint := fmt.Sprintf("/zones/%s/api_gateway/operations/%s", testZoneID, testAPIShieldOperationId)
-	response := `{
-		"success" : true,
-		"errors": [],
-		"messages": [],
-		"result": {
-			"operation_id": "9def2cb0-3ed0-4737-92ca-f09efa4718fd",
-			"method": "POST",
-			"host": "api.cloudflare.com",
-			"endpoint": "/client/v4/zones",
-			"last_updated": "2023-03-02T15:46:06.000000Z",
-			"features":{
-				"thresholds":{},
-				"parameter_schemas":{}
-			}
-		}
-	}`
-
-	tests := []struct {
-		name           string
-		getParams      GetAPIShieldOperationParams
-		expectedParams url.Values
-	}{
-		{
-			name: "one feature",
-			getParams: GetAPIShieldOperationParams{
-				OperationID: testAPIShieldOperationId,
-				Features:    []string{"thresholds"},
-			},
-			expectedParams: url.Values{
-				"feature": []string{"thresholds"},
-			},
-		},
-		{
-			name: "more than one feature",
-			getParams: GetAPIShieldOperationParams{
-				OperationID: testAPIShieldOperationId,
-				Features:    []string{"thresholds", "parameter_schemas"},
-			},
-			expectedParams: url.Values{
-				"feature": []string{"thresholds", "parameter_schemas"},
-			},
-		},
-	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			setup()
-			t.Cleanup(teardown)
-			handler := func(w http.ResponseWriter, r *http.Request) {
-				require.Equal(t, http.MethodGet, r.Method, "Expected method 'GET', got %s", r.Method)
-				require.Equal(t, test.expectedParams, r.URL.Query())
-				w.Header().Set("content-type", "application/json")
-				fmt.Fprint(w, response)
-			}
-
-			mux.HandleFunc(endpoint, handler)
-
-			actual, err := client.GetAPIShieldOperation(
-				context.Background(),
-				ZoneIdentifier(testZoneID),
-				test.getParams,
-			)
-
-			time := time.Date(2023, time.March, 2, 15, 46, 6, 0, time.UTC)
-			expected := &APIShieldOperation{
-				APIShieldBasicOperation: APIShieldBasicOperation{
-					Method:   "POST",
-					Host:     "api.cloudflare.com",
-					Endpoint: "/client/v4/zones",
-				},
-				ID:          "9def2cb0-3ed0-4737-92ca-f09efa4718fd",
-				LastUpdated: &time,
-				Features: map[string]any{
-					"thresholds":        map[string]any{},
-					"parameter_schemas": map[string]any{},
-				},
-			}
-
-			if assert.NoError(t, err) {
-				assert.Equal(t, expected, actual)
-			}
-		})
-	}
-}
-
-func TestListAPIShieldOperations(t *testing.T) {
-	endpoint := fmt.Sprintf("/zones/%s/api_gateway/operations", testZoneID)
-	response := `{
-		"success" : true,
-		"errors": [],
-		"messages": [],
-		"result": [
-			{
-				"operation_id": "9def2cb0-3ed0-4737-92ca-f09efa4718fd",
-				"method": "POST",
-				"host": "api.cloudflare.com",
-				"endpoint": "/client/v4/zones",
-				"last_updated": "2023-03-02T15:46:06.000000Z"
-			}
-
-		],
-		"result_info": {
-			"page": 3,
-			"per_page": 20,
-			"count": 1,
-			"total_count": 2000
-		}
-	}`
-
-	setup()
-	t.Cleanup(teardown)
-	handler := func(w http.ResponseWriter, r *http.Request) {
-		require.Equal(t, http.MethodGet, r.Method, "Expected method 'GET', got %s", r.Method)
-		require.Empty(t, r.URL.Query())
-		w.Header().Set("content-type", "application/json")
-		fmt.Fprint(w, response)
-	}
-
-	mux.HandleFunc(endpoint, handler)
-
-	actual, actualResultInfo, err := client.ListAPIShieldOperations(
-		context.Background(),
-		ZoneIdentifier(testZoneID),
-		ListAPIShieldOperationsParams{},
-	)
-
-	time := time.Date(2023, time.March, 2, 15, 46, 6, 0, time.UTC)
-	expectedOps := []APIShieldOperation{
-		{
-			APIShieldBasicOperation: APIShieldBasicOperation{
-				Method:   "POST",
-				Host:     "api.cloudflare.com",
-				Endpoint: "/client/v4/zones",
-			},
-			ID:          "9def2cb0-3ed0-4737-92ca-f09efa4718fd",
-			LastUpdated: &time,
-			Features:    nil,
-		},
-	}
-
-	expectedResultInfo := ResultInfo{
-		Page:    3,
-		PerPage: 20,
-		Count:   1,
-		Total:   2000,
-	}
-
-	if assert.NoError(t, err) {
-		assert.Equal(t, expectedOps, actual)
-		assert.Equal(t, expectedResultInfo, actualResultInfo)
-	}
-}
-
-func TestListAPIShieldOperationsWithParams(t *testing.T) {
-	endpoint := fmt.Sprintf("/zones/%s/api_gateway/operations", testZoneID)
-	response := `{
-		"success" : true,
-		"errors": [],
-		"messages": [],
-		"result": [
-			{
-				"operation_id": "9def2cb0-3ed0-4737-92ca-f09efa4718fd",
-				"method": "POST",
-				"host": "api.cloudflare.com",
-				"endpoint": "/client/v4/zones",
-				"last_updated": "2023-03-02T15:46:06.000000Z",
-				"features": {
-					"thresholds": {}
-                }
-			}
-		],
-		"result_info": {
-			"page": 3,
-			"per_page": 20,
-			"count": 1,
-			"total_count": 2000
-		}
-	}`
-
-	tests := []struct {
-		name           string
-		params         ListAPIShieldOperationsParams
-		expectedParams url.Values
-	}{
-		{
-			name: "all params",
-			params: ListAPIShieldOperationsParams{
-				Features:  []string{"thresholds", "parameter_schemas"},
-				Direction: "desc",
-				OrderBy:   "host",
-				APIShieldListOperationsFilters: APIShieldListOperationsFilters{
-					Hosts:    []string{"api.cloudflare.com", "developers.cloudflare.com"},
-					Methods:  []string{"GET", "PUT"},
-					Endpoint: "/client",
-				},
-				PaginationOptions: PaginationOptions{
-					Page:    1,
-					PerPage: 25,
-				},
-			},
-			expectedParams: url.Values{
-				"feature":   []string{"thresholds", "parameter_schemas"},
-				"direction": []string{"desc"},
-				"order":     []string{"host"},
-				"host":      []string{"api.cloudflare.com", "developers.cloudflare.com"},
-				"method":    []string{"GET", "PUT"},
-				"endpoint":  []string{"/client"},
-				"page":      []string{"1"},
-				"per_page":  []string{"25"},
-			},
-		},
-		{
-			name: "features only",
-			params: ListAPIShieldOperationsParams{
-				Features: []string{"thresholds", "parameter_schemas"},
-			},
-			expectedParams: url.Values{
-				"feature": []string{"thresholds", "parameter_schemas"},
-			},
-		},
-		{
-			name: "direction only",
-			params: ListAPIShieldOperationsParams{
-				Direction: "desc",
-			},
-			expectedParams: url.Values{
-				"direction": []string{"desc"},
-			},
-		},
-		{
-			name: "order only",
-			params: ListAPIShieldOperationsParams{
-				OrderBy: "host",
-			},
-			expectedParams: url.Values{
-				"order": []string{"host"},
-			},
-		},
-		{
-			name: "hosts only",
-			params: ListAPIShieldOperationsParams{
-				APIShieldListOperationsFilters: APIShieldListOperationsFilters{
-					Hosts: []string{"api.cloudflare.com", "developers.cloudflare.com"},
-				},
-			},
-			expectedParams: url.Values{
-				"host": []string{"api.cloudflare.com", "developers.cloudflare.com"},
-			},
-		},
-		{
-			name: "methods only",
-			params: ListAPIShieldOperationsParams{
-				APIShieldListOperationsFilters: APIShieldListOperationsFilters{
-					Methods: []string{"GET", "PUT"},
-				},
-			},
-			expectedParams: url.Values{
-				"method": []string{"GET", "PUT"},
-			},
-		},
-		{
-			name: "endpoint only",
-			params: ListAPIShieldOperationsParams{
-				APIShieldListOperationsFilters: APIShieldListOperationsFilters{
-					Endpoint: "/client",
-				},
-			},
-			expectedParams: url.Values{
-				"endpoint": []string{"/client"},
-			},
-		},
-		{
-			name: "pagination only",
-			params: ListAPIShieldOperationsParams{
-				PaginationOptions: PaginationOptions{
-					Page:    1,
-					PerPage: 25,
-				},
-			},
-			expectedParams: url.Values{
-				"page":     []string{"1"},
-				"per_page": []string{"25"},
-			},
-		},
-	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			setup()
-			t.Cleanup(teardown)
-			handler := func(w http.ResponseWriter, r *http.Request) {
-				require.Equal(t, http.MethodGet, r.Method, "Expected method 'GET', got %s", r.Method)
-				require.Equal(t, test.expectedParams, r.URL.Query())
-				w.Header().Set("content-type", "application/json")
-				fmt.Fprint(w, response)
-			}
-
-			mux.HandleFunc(endpoint, handler)
-
-			actual, _, err := client.ListAPIShieldOperations(
-				context.Background(),
-				ZoneIdentifier(testZoneID),
-				test.params,
-			)
-
-			time := time.Date(2023, time.March, 2, 15, 46, 6, 0, time.UTC)
-			expected := []APIShieldOperation{
-				{
-					APIShieldBasicOperation: APIShieldBasicOperation{
-						Method:   "POST",
-						Host:     "api.cloudflare.com",
-						Endpoint: "/client/v4/zones",
-					},
-					ID:          "9def2cb0-3ed0-4737-92ca-f09efa4718fd",
-					LastUpdated: &time,
-					Features: map[string]any{
-						"thresholds": map[string]any{},
-					},
-				},
-			}
-
-			if assert.NoError(t, err) {
-				assert.Equal(t, expected, actual)
-			}
-		})
-	}
-}
-
-func TestCreateAPIShieldOperations(t *testing.T) {
-	setup()
-	t.Cleanup(teardown)
-
-	endpoint := fmt.Sprintf("/zones/%s/api_gateway/operations", testZoneID)
-	response := `{
-		"success" : true,
-		"errors": [],
-		"messages": [],
-		"result": [
-			{
-				"operation_id": "9def2cb0-3ed0-4737-92ca-f09efa4718fd",
-				"method": "POST",
-				"host": "api.cloudflare.com",
-				"endpoint": "/client/v4/zones",
-				"last_updated": "2023-03-02T15:46:06.000000Z"
-			}
-		]
-	}`
-
-	handler := func(w http.ResponseWriter, r *http.Request) {
-		require.Equal(t, http.MethodPost, r.Method, "Expected method 'POST', got %s", r.Method)
-
-		body, err := io.ReadAll(r.Body)
-		require.NoError(t, err)
-		require.Equal(t, []byte(`[{"method":"POST","host":"api.cloudflare.com","endpoint":"/client/v4/zones"}]`), body)
-
-		w.Header().Set("content-type", "application/json")
-		fmt.Fprint(w, response)
-	}
-
-	mux.HandleFunc(endpoint, handler)
-
-	actual, err := client.CreateAPIShieldOperations(
-		context.Background(),
-		ZoneIdentifier(testZoneID),
-		CreateAPIShieldOperationsParams{
-			Operations: []APIShieldBasicOperation{
-				{
-					Method:   "POST",
-					Host:     "api.cloudflare.com",
-					Endpoint: "/client/v4/zones",
-				},
-			},
-		},
-	)
-
-	time := time.Date(2023, time.March, 2, 15, 46, 6, 0, time.UTC)
-	expected := []APIShieldOperation{
-		{
-			APIShieldBasicOperation: APIShieldBasicOperation{
-				Method:   "POST",
-				Host:     "api.cloudflare.com",
-				Endpoint: "/client/v4/zones",
-			},
-			ID:          "9def2cb0-3ed0-4737-92ca-f09efa4718fd",
-			LastUpdated: &time,
-			Features:    nil,
-		},
-	}
-
-	if assert.NoError(t, err) {
-		assert.Equal(t, expected, actual)
-	}
-}
-
-func TestDeleteAPIShieldOperation(t *testing.T) {
-	setup()
-	t.Cleanup(teardown)
-
-	endpoint := fmt.Sprintf("/zones/%s/api_gateway/operations/%s", testZoneID, testAPIShieldOperationId)
-	response := `{"result":{},"success":true,"errors":[],"messages":[]}`
-
-	handler := func(w http.ResponseWriter, r *http.Request) {
-		require.Equal(t, http.MethodDelete, r.Method, "Expected method 'DELETE', got %s", r.Method)
-		require.Empty(t, r.URL.Query())
-		w.Header().Set("content-type", "application/json")
-		fmt.Fprint(w, response)
-	}
-
-	mux.HandleFunc(endpoint, handler)
-
-	err := client.DeleteAPIShieldOperation(
-		context.Background(),
-		ZoneIdentifier(testZoneID),
-		DeleteAPIShieldOperationParams{
-			OperationID: testAPIShieldOperationId,
-		},
-	)
-
-	assert.NoError(t, err)
-}
diff --git a/observatory_test.go b/observatory_test.go
deleted file mode 100644
index 98ff51209..000000000
--- a/observatory_test.go
+++ /dev/null
@@ -1,452 +0,0 @@
-package cloudflare
-
-import (
-	"context"
-	"fmt"
-	"io"
-	"net/http"
-	"strings"
-	"testing"
-	"time"
-
-	"github.com/stretchr/testify/assert"
-)
-
-var testURL = "example.com"
-var region = "us-central1"
-var regionLabel = "Iowa, USA"
-var frequency = "DAILY"
-var observatoryTestID = "52cc96c9-b709-4ffe-9048-338853d3db46"
-var date = time.Now().UTC()
-
-var pageJSON = fmt.Sprintf(`
-{
-  "url": "%[1]s",
-  "region": {
-    "value": "%[2]s",
-    "label": "%[3]s"
-  },
-  "scheduleFrequency": "%[4]s",
-  "tests": [
-    {
-      "id": "%[5]s",
-      "date": "%[6]s",
-      "url": "%[1]s",
-      "scheduleFrequency": "%[4]s",
-      "region": {
-        "value": "%[2]s",
-        "label": "%[3]s"
-      },
-      "mobileReport": {
-        "performanceScore": 100,
-        "ttfb": 10,
-        "fcp": 10,
-        "lcp": 10,
-        "tti": 10,
-        "tbt": 10,
-        "si": 10,
-        "cls": 0.10,
-        "state": "COMPLETED",
-        "deviceType": "DESKTOP"
-      },
-      "desktopReport": {
-        "performanceScore": 100,
-        "ttfb": 10,
-        "fcp": 10,
-        "lcp": 10,
-        "tti": 10,
-        "tbt": 10,
-        "si": 10,
-        "cls": 0.10,
-        "state": "COMPLETED",
-        "deviceType": "DESKTOP"
-      }
-    }
-  ]
-}`, testURL, region, regionLabel, frequency, observatoryTestID, date.Format(time.RFC3339Nano))
-
-var scheduledPageTestJSON = fmt.Sprintf(`
-{
-  "schedule": {
-    "url": "%[1]s",
-    "region": "%[2]s",
-    "frequency": "%[3]s"
-  },
-  "test": %[4]s
-}
-`, testURL, region, frequency, pageTestJSON)
-
-var scheduleJSON = fmt.Sprintf(`
-{
-  "url": "%[1]s",
-  "region": "%[2]s",
-  "frequency": "%[3]s"
-}
-`, testURL, region, frequency)
-
-var pageTestJSON = fmt.Sprintf(`
-{
-  "id": "%[1]s",
-  "date": "%[2]s",
-  "url": "%[3]s",
-  "region": {
-    "value": "%[4]s",
-    "label": "%[5]s"
-  },
-  "scheduleFrequency": "%[6]s",
-  "mobileReport": %[7]s,
-  "desktopReport": %[7]s
-}`, observatoryTestID, date.Format(time.RFC3339Nano), testURL, region, regionLabel, frequency, reportJSON, reportJSON)
-
-var reportJSON = `
-{
-  "state": "COMPLETED",
-  "deviceType": "DESKTOP",
-  "performanceScore": 100,
-  "ttfb": 10,
-  "fcp": 10,
-  "lcp": 10,
-  "tti": 10,
-  "tbt": 10,
-  "si": 10,
-  "cls": 0.10
-}
-`
-
-var report = ObservatoryLighthouseReport{
-	PerformanceScore: 100,
-	State:            "COMPLETED",
-	DeviceType:       "DESKTOP",
-	TTFB:             10,
-	FCP:              10,
-	LCP:              10,
-	TTI:              10,
-	TBT:              10,
-	SI:               10,
-	CLS:              0.10,
-	Error:            nil,
-}
-
-var page = ObservatoryPage{
-	URL: testURL,
-	Region: labeledRegion{
-		Value: region,
-		Label: regionLabel,
-	},
-	ScheduleFrequency: frequency,
-	Tests: []ObservatoryPageTest{
-		pageTest,
-	},
-}
-var pageTest = ObservatoryPageTest{
-	ID:   observatoryTestID,
-	Date: &date,
-	URL:  testURL,
-	Region: labeledRegion{
-		Value: region,
-		Label: regionLabel,
-	},
-	ScheduleFrequency: &frequency,
-	MobileReport:      report,
-	DesktopReport:     report,
-}
-
-var scheduledPageTest = ObservatoryScheduledPageTest{
-	Schedule: ObservatorySchedule{
-		URL:       testURL,
-		Region:    region,
-		Frequency: frequency,
-	},
-	Test: pageTest,
-}
-
-var schedule = ObservatorySchedule{
-	URL:       testURL,
-	Region:    region,
-	Frequency: frequency,
-}
-
-func TestListObservatoryPages(t *testing.T) {
-	setup()
-	defer teardown()
-
-	handler := 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")
-		fmt.Fprintf(w, `{
-			  "success": true,
-			  "errors": [],
-			  "messages": [],
-			  "result": [
-			    %s
-			  ]
-			}
-		`, pageJSON)
-	}
-	mux.HandleFunc("/zones/"+testZoneID+"/speed_api/pages", handler)
-	want := []ObservatoryPage{
-		page,
-	}
-	pages, err := client.ListObservatoryPages(context.Background(), ZoneIdentifier(testZoneID), ListObservatoryPagesParams{})
-	if assert.NoError(t, err) {
-		assert.Equal(t, want, pages)
-	}
-}
-
-func TestObservatoryPageTrend(t *testing.T) {
-	setup()
-	defer teardown()
-
-	handler := func(w http.ResponseWriter, r *http.Request) {
-		assert.Equal(t, http.MethodGet, r.Method, "Expected method 'GET', got %s", r.Method)
-		assert.Equal(t, "DESKTOP", r.URL.Query().Get("deviceType"))
-		assert.Equal(t, "America/Chicago", r.URL.Query().Get("tz"))
-		assert.Equal(t, "fcp,lcp", r.URL.Query().Get("metrics"))
-		w.Header().Set("content-type", "application/json")
-		fmt.Fprintf(w, `{
-			  "success": true,
-			  "errors": [],
-			  "messages": [],
-			  "result": {
-			    "performanceScore": [null, 100],
-			    "ttfb": [null, 10],
-			    "fcp": [null, 10],
-			    "lcp": [null, 10],
-			    "tti": [null, 10],
-			    "tbt": [null, 10],
-			    "si": [null, 10],
-			    "cls": [null, 0.10]
-			  }
-			}
-		`)
-	}
-	mux.HandleFunc("/zones/"+testZoneID+"/speed_api/pages/"+testURL+"/trend", handler)
-	want := ObservatoryPageTrend{
-		PerformanceScore: []*int{nil, IntPtr(100)},
-		TTFB:             []*int{nil, IntPtr(10)},
-		FCP:              []*int{nil, IntPtr(10)},
-		LCP:              []*int{nil, IntPtr(10)},
-		TTI:              []*int{nil, IntPtr(10)},
-		TBT:              []*int{nil, IntPtr(10)},
-		SI:               []*int{nil, IntPtr(10)},
-		CLS:              []*float64{nil, Float64Ptr(0.10)},
-	}
-	trend, err := client.GetObservatoryPageTrend(context.Background(), ZoneIdentifier(testZoneID), GetObservatoryPageTrendParams{
-		URL:        testURL,
-		Region:     region,
-		DeviceType: "DESKTOP",
-		Start:      &date,
-		End:        &date,
-		Timezone:   "America/Chicago",
-		Metrics:    []string{"fcp,lcp"},
-	})
-	if assert.NoError(t, err) {
-		assert.Equal(t, &want, trend)
-	}
-}
-
-func TestListObservatoryPageTests(t *testing.T) {
-	setup()
-	defer teardown()
-
-	handler := func(w http.ResponseWriter, r *http.Request) {
-		assert.Equal(t, http.MethodGet, r.Method, "Expected method 'GET', got %s", r.Method)
-		assert.Equal(t, region, r.URL.Query().Get("region"))
-		assert.Equal(t, "1", r.URL.Query().Get("page"))
-		assert.Equal(t, "10", r.URL.Query().Get("per_page"))
-		w.Header().Set("content-type", "application/json")
-		fmt.Fprintf(w, `{
-			  "success": true,
-			  "errors": [],
-			  "messages": [],
-			  "result": [%s]
-			}
-		`, pageTestJSON)
-	}
-	mux.HandleFunc("/zones/"+testZoneID+"/speed_api/pages/"+testURL+"/tests", handler)
-	want := []ObservatoryPageTest{
-		pageTest,
-	}
-	tests, _, err := client.ListObservatoryPageTests(context.Background(), ZoneIdentifier(testZoneID), ListObservatoryPageTestParams{
-		URL: testURL,
-		ResultInfo: ResultInfo{
-			Page:    1,
-			PerPage: 10,
-		},
-		Region: region,
-	})
-	if assert.NoError(t, err) {
-		assert.Equal(t, want, tests)
-	}
-}
-
-func TestCreateObservatoryPageTest(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)
-		b, err := io.ReadAll(r.Body)
-		assert.NoError(t, err)
-		assert.True(t, strings.Contains(string(b), region))
-		w.Header().Set("content-type", "application/json")
-		fmt.Fprintf(w, `{
-			  "success": true,
-			  "errors": [],
-			  "messages": [],
-			  "result": %s
-			}
-		`, pageTestJSON)
-	}
-	mux.HandleFunc("/zones/"+testZoneID+"/speed_api/pages/"+testURL+"/tests", handler)
-	want := pageTest
-	test, err := client.CreateObservatoryPageTest(context.Background(), ZoneIdentifier(testZoneID), CreateObservatoryPageTestParams{
-		URL: testURL,
-		Settings: CreateObservatoryPageTestSettings{
-			Region: region,
-		},
-	})
-	if assert.NoError(t, err) {
-		assert.Equal(t, &want, test)
-	}
-}
-
-func TestDeleteObservatoryPageTests(t *testing.T) {
-	setup()
-	defer teardown()
-
-	handler := func(w http.ResponseWriter, r *http.Request) {
-		assert.Equal(t, http.MethodDelete, r.Method, "Expected method 'DELETE', got %s", r.Method)
-		assert.Equal(t, region, r.URL.Query().Get("region"))
-		w.Header().Set("content-type", "application/json")
-		fmt.Fprintf(w, `{
-			  "success": true,
-			  "errors": [],
-			  "messages": [],
-			  "result": {
-                "count": 2
-              }
-			}
-		`)
-	}
-	mux.HandleFunc("/zones/"+testZoneID+"/speed_api/pages/"+testURL+"/tests", handler)
-	want := 2
-	count, err := client.DeleteObservatoryPageTests(context.Background(), ZoneIdentifier(testZoneID), DeleteObservatoryPageTestsParams{
-		URL:    testURL,
-		Region: region,
-	})
-	if assert.NoError(t, err) {
-		assert.Equal(t, &want, count)
-	}
-}
-
-func TestGetObservatoryPageTest(t *testing.T) {
-	setup()
-	defer teardown()
-
-	handler := 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")
-		fmt.Fprintf(w, `{
-			  "success": true,
-			  "errors": [],
-			  "messages": [],
-			  "result": %s
-			}
-		`, pageTestJSON)
-	}
-	mux.HandleFunc("/zones/"+testZoneID+"/speed_api/pages/"+testURL+"/tests/"+observatoryTestID, handler)
-	want := pageTest
-	test, err := client.GetObservatoryPageTest(context.Background(), ZoneIdentifier(testZoneID), GetObservatoryPageTestParams{
-		TestID: observatoryTestID,
-		URL:    testURL,
-	})
-	if assert.NoError(t, err) {
-		assert.Equal(t, &want, test)
-	}
-}
-
-func TestCreateObservatoryScheduledPageTest(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)
-		assert.Equal(t, frequency, r.URL.Query().Get("frequency"))
-		assert.Equal(t, region, r.URL.Query().Get("region"))
-		w.Header().Set("content-type", "application/json")
-		fmt.Fprintf(w, `{
-			  "success": true,
-			  "errors": [],
-			  "messages": [],
-			  "result": %s
-			}
-		`, scheduledPageTestJSON)
-	}
-	mux.HandleFunc("/zones/"+testZoneID+"/speed_api/schedule/"+testURL, handler)
-	want := scheduledPageTest
-	pages, err := client.CreateObservatoryScheduledPageTest(context.Background(), ZoneIdentifier(testZoneID), CreateObservatoryScheduledPageTestParams{
-		Frequency: frequency,
-		URL:       testURL,
-		Region:    region,
-	})
-	if assert.NoError(t, err) {
-		assert.Equal(t, &want, pages)
-	}
-}
-
-func TestObservatoryScheduledPageTest(t *testing.T) {
-	setup()
-	defer teardown()
-
-	handler := func(w http.ResponseWriter, r *http.Request) {
-		assert.Equal(t, http.MethodGet, r.Method, "Expected method 'GET', got %s", r.Method)
-		assert.Equal(t, region, r.URL.Query().Get("region"))
-		w.Header().Set("content-type", "application/json")
-		fmt.Fprintf(w, `{
-			  "success": true,
-			  "errors": [],
-			  "messages": [],
-			  "result": %s
-			}
-		`, scheduleJSON)
-	}
-	mux.HandleFunc("/zones/"+testZoneID+"/speed_api/schedule/"+testURL, handler)
-	want := schedule
-	schedule, err := client.GetObservatoryScheduledPageTest(context.Background(), ZoneIdentifier(testZoneID), GetObservatoryScheduledPageTestParams{
-		URL:    testURL,
-		Region: region,
-	})
-	if assert.NoError(t, err) {
-		assert.Equal(t, &want, schedule)
-	}
-}
-
-func TestDeleteObservatoryScheduledPageTest(t *testing.T) {
-	setup()
-	defer teardown()
-
-	handler := func(w http.ResponseWriter, r *http.Request) {
-		assert.Equal(t, http.MethodDelete, r.Method, "Expected method 'DELETE', got %s", r.Method)
-		assert.Equal(t, region, r.URL.Query().Get("region"))
-		w.Header().Set("content-type", "application/json")
-		fmt.Fprintf(w, `{
-			  "success": true,
-			  "errors": [],
-			  "messages": [],
-			  "result": { 
-                "count": 2
-              }
-			}
-		`)
-	}
-	mux.HandleFunc("/zones/"+testZoneID+"/speed_api/schedule/"+testURL, handler)
-	want := 2
-	count, err := client.DeleteObservatoryScheduledPageTest(context.Background(), ZoneIdentifier(testZoneID), DeleteObservatoryScheduledPageTestParams{
-		URL:    testURL,
-		Region: region,
-	})
-	if assert.NoError(t, err) {
-		assert.Equal(t, &want, count)
-	}
-}