From 26caa1777244af9ab4cda4df84cd2b2c152b8274 Mon Sep 17 00:00:00 2001 From: Jaideep Padhye Date: Fri, 7 Aug 2026 16:10:26 -0700 Subject: [PATCH 01/13] SYN-6889: report coverage and test results in CI, add README badges Run both syntheticsclient (v1) and syntheticsclientv2 (v2) in CI test execution instead of just v2, rename the misleadingly-named testacc target (there is no live acceptance suite in this repo) to test-cover, and publish a combined coverage profile plus go-test-coverage/dorny test-reporter checks so pass/fail counts and coverage % are visible directly on the PR. Coverage threshold is set to the current combined baseline (71%) as a regression guard; raise to 90% once outstanding test coverage work lands separately. Remove sonar-project.properties and the dead sonarqube Make target: no SonarQube project exists for this repo, and the file was internally inconsistent (v1 sources scored against v2-only coverage). Add Release/CI/Build/License badges to README.md. Co-Authored-By: Claude --- .github/workflows/ci.yml | 29 ++++++++++++++++++++++------- .gitignore | 2 ++ .testcoverage.yml | 7 +++++++ Makefile | 22 +++++++++------------- README.md | 6 ++++++ coverage.txt | 1 - sonar-project.properties | 4 ---- 7 files changed, 46 insertions(+), 25 deletions(-) create mode 100644 .testcoverage.yml delete mode 100644 coverage.txt delete mode 100644 sonar-project.properties diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9ae64d5..f0163dd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,15 +5,13 @@ name: CI Checks # Optionally, you can turn it on using a schedule for regular testing. on: pull_request: - paths-ignore: - - 'README.md' push: - paths-ignore: - - 'README.md' -# Testing only needs permissions to read the repository contents. +# Testing needs to read repository contents and write check runs (test-reporter) +# and step summaries (coverage report). permissions: contents: read + checks: write jobs: # Ensure project builds before running testing matrix @@ -33,5 +31,22 @@ jobs: with: version: latest - run: make lint - - run: make testacc - - run: go build -v ./syntheticsclientv2 \ No newline at end of file + - run: make test-cover + - name: Report test results + uses: dorny/test-reporter@a43b3a5f7366b97d083190328d2c652e1a8b6aa2 # v3 + if: ${{ !cancelled() }} + with: + name: Go Tests + path: test-results.json + reporter: golang-json + - name: Check test coverage + id: coverage + uses: vladopajic/go-test-coverage@f94bcf0d6b9fa5fb8b783830b22648f6c17475e2 # v2 + with: + config: ./.testcoverage.yml + - name: Publish coverage summary + if: ${{ !cancelled() }} + run: | + echo "### Coverage" >> "$GITHUB_STEP_SUMMARY" + go tool cover -func=coverage.txt | tail -1 >> "$GITHUB_STEP_SUMMARY" + - run: go build -v ./... diff --git a/.gitignore b/.gitignore index ae03155..a148dac 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,8 @@ # Output of the go coverage tool, specifically when used with LiteIDE *.out +coverage.txt +test-results.json # Dependency directories (remove the comment below to include it) # vendor/ diff --git a/.testcoverage.yml b/.testcoverage.yml new file mode 100644 index 0000000..e4c3b88 --- /dev/null +++ b/.testcoverage.yml @@ -0,0 +1,7 @@ +profile: coverage.txt + +threshold: + # Ratchet floor at today's combined baseline (syntheticsclient + syntheticsclientv2). + # Raise to 90 once SYN-6889's test coverage work lands; until then this only + # guards against regressions. + total: 71 diff --git a/Makefile b/Makefile index 9f141cc..f4913de 100644 --- a/Makefile +++ b/Makefile @@ -1,19 +1,18 @@ -.PHONY: default all build clean test fmtcheck testacc sonarqube +.PHONY: default all build clean test fmtcheck test-cover -PKG_NAME=syntheticsclientv2 -FILES=./syntheticsclientv2/... +FILES=./... -default: test +default: test -all: clean build test +all: clean build test build: fmtcheck - go build -tags=unit_tests + go build -tags=unit_tests ./... clean: @echo "==> Cleaning out old builds " go clean - rm -rf coverage.txt .sonar .scannerwork + rm -rf coverage.txt test-results.json fmt: @@ -30,9 +29,6 @@ test: fmtcheck @echo "==> Running all tests" go test $(FILES) -v -tags=unit_tests -timeout=30s -parallel=4 -cover -testacc: clean fmtcheck - @echo "==> Running all tests" - go test $(FILES) -v -tags=unit_tests -timeout=30s -parallel=8 -cover -coverprofile coverage.txt - -sonarqube: testacc - docker run -it -v "${PWD}:/usr/src" sonarsource/sonar-scanner-cli +test-cover: clean fmtcheck + @echo "==> Running all tests with coverage and JSON output" + go test $(FILES) -tags=unit_tests -timeout=30s -parallel=8 -json -cover -covermode=atomic -coverprofile coverage.txt > test-results.json diff --git a/README.md b/README.md index bbc5de5..318882f 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,10 @@ # syntheticsclient + +[![Release](https://img.shields.io/github/v/release/splunk/syntheticsclient)](https://github.com/splunk/syntheticsclient/releases) +[![CI Checks](https://img.shields.io/github/actions/workflow/status/splunk/syntheticsclient/ci.yml?branch=v2&label=CI)](https://github.com/splunk/syntheticsclient/actions/workflows/ci.yml?query=branch%3Av2) +[![Build](https://img.shields.io/github/actions/workflow/status/splunk/syntheticsclient/ci.yml?branch=v2&label=build)](https://github.com/splunk/syntheticsclient/actions/workflows/ci.yml?query=branch%3Av2) +[![License](https://img.shields.io/github/license/splunk/syntheticsclient)](https://github.com/splunk/syntheticsclient/blob/v2/LICENSE) + A Splunk Synthetics for Splunk Observability (Formerly Rigor) client for golang. ## Installation diff --git a/coverage.txt b/coverage.txt deleted file mode 100644 index 4dd54a3..0000000 --- a/coverage.txt +++ /dev/null @@ -1 +0,0 @@ -mode: set \ No newline at end of file diff --git a/sonar-project.properties b/sonar-project.properties deleted file mode 100644 index 85a9be8..0000000 --- a/sonar-project.properties +++ /dev/null @@ -1,4 +0,0 @@ -sonar.projectKey=syntheticsclient -sonar.exclusions=coverage*,**/*_test.go -sonar.sources=./syntheticsclient/ -sonar.go.coverage.reportPaths=coverage.txt From d6f27ec70796c6db3bd476ac81f9a67304bdce64 Mon Sep 17 00:00:00 2001 From: Jaideep Padhye Date: Fri, 7 Aug 2026 16:22:33 -0700 Subject: [PATCH 02/13] SYN-6889: exclude deprecated syntheticsclient (v1) from CI and coverage v1 is slated for deprecation, so it should not run in tests, contribute to the build, or count toward the coverage gate. Scope Makefile/CI back to syntheticsclientv2 only and re-baseline the coverage threshold at its current 73.5%. Verified locally that go test's non-zero exit on a failing test propagates through `make test-cover`'s output redirection, so the CI job fails on any test failure independent of the coverage gate. --- .github/workflows/ci.yml | 2 +- .testcoverage.yml | 5 +++-- Makefile | 6 ++++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f0163dd..16facaa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -49,4 +49,4 @@ jobs: run: | echo "### Coverage" >> "$GITHUB_STEP_SUMMARY" go tool cover -func=coverage.txt | tail -1 >> "$GITHUB_STEP_SUMMARY" - - run: go build -v ./... + - run: go build -v ./syntheticsclientv2/... diff --git a/.testcoverage.yml b/.testcoverage.yml index e4c3b88..e5a51f7 100644 --- a/.testcoverage.yml +++ b/.testcoverage.yml @@ -1,7 +1,8 @@ profile: coverage.txt threshold: - # Ratchet floor at today's combined baseline (syntheticsclient + syntheticsclientv2). + # Ratchet floor at today's syntheticsclientv2 baseline. syntheticsclient (v1) + # is deprecated and excluded from tests/coverage entirely (see Makefile). # Raise to 90 once SYN-6889's test coverage work lands; until then this only # guards against regressions. - total: 71 + total: 73 diff --git a/Makefile b/Makefile index f4913de..b8a379a 100644 --- a/Makefile +++ b/Makefile @@ -1,13 +1,15 @@ .PHONY: default all build clean test fmtcheck test-cover -FILES=./... +# syntheticsclient (v1) is deprecated and excluded from tests, builds, and +# coverage; only syntheticsclientv2 is exercised here. +FILES=./syntheticsclientv2/... default: test all: clean build test build: fmtcheck - go build -tags=unit_tests ./... + go build -tags=unit_tests $(FILES) clean: @echo "==> Cleaning out old builds " From 58b3a056f6b87b3facb1b3cd06d394abac77b2b5 Mon Sep 17 00:00:00 2001 From: Jaideep Padhye Date: Fri, 7 Aug 2026 16:33:48 -0700 Subject: [PATCH 03/13] SYN-6889: post coverage diff as a PR comment The coverage gate ran but never surfaced its number on the PR itself. Wire vladopajic/go-test-coverage's built-in diff/comment support: upload a coverage breakdown artifact on every v2 push, download it on PR runs to diff against, and post the resulting report via thollander/actions-comment-pull-request. The coverage step now continues on error so the comment posts before a threshold miss fails the job. --- .github/workflows/ci.yml | 42 +++++++++++++++++++++++++++++++++++++++- .gitignore | 1 + Makefile | 2 +- 3 files changed, 43 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 16facaa..945ccd7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,10 +8,14 @@ on: push: # Testing needs to read repository contents and write check runs (test-reporter) -# and step summaries (coverage report). +# and step summaries (coverage report). actions:read lets PR runs download the +# base branch's coverage breakdown artifact; pull-requests:write lets the +# coverage-diff report be posted as a PR comment. permissions: contents: read checks: write + actions: read + pull-requests: write jobs: # Ensure project builds before running testing matrix @@ -39,11 +43,47 @@ jobs: name: Go Tests path: test-results.json reporter: golang-json + # Fetch v2's (the base branch's) last coverage breakdown so PR runs can + # report how this change moves coverage, not just its absolute value. + - name: Download base branch coverage breakdown + id: download-main-breakdown + uses: dawidd6/action-download-artifact@b6e2e70617bc3265edd6dab6c906732b2f1ae151 # v21 + with: + branch: v2 + workflow_conclusion: success + name: v2.breakdown + if_no_artifact_found: warn - name: Check test coverage id: coverage uses: vladopajic/go-test-coverage@f94bcf0d6b9fa5fb8b783830b22648f6c17475e2 # v2 + continue-on-error: true # fail after the coverage comment is posted below with: config: ./.testcoverage.yml + breakdown-file-name: ${{ github.ref_name == 'v2' && 'v2.breakdown' || '' }} + diff-base-breakdown-file-name: ${{ steps.download-main-breakdown.outputs.found_artifact == 'true' && 'v2.breakdown' || '' }} + - name: Upload coverage breakdown + if: ${{ github.ref_name == 'v2' }} + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: v2.breakdown + path: v2.breakdown + if-no-files-found: error + # No status function here: leaving the implicit success() check in + # place means this (and the coverage step it reads outputs from) is + # skipped if make test-cover failed, instead of erroring on a missing + # report from a skipped step. + - name: Post coverage report to PR + if: ${{ github.event_name == 'pull_request' }} + uses: thollander/actions-comment-pull-request@24bffb9b452ba05a4f3f77933840a6a841d1b32b # v3.0.1 + with: + comment-tag: coverage-report + message: | + go-test-coverage report: + ``` + ${{ fromJSON(steps.coverage.outputs.report) }}``` + - name: Fail if coverage threshold not met + if: ${{ steps.coverage.outcome == 'failure' }} + run: echo "coverage check failed" && exit 1 - name: Publish coverage summary if: ${{ !cancelled() }} run: | diff --git a/.gitignore b/.gitignore index a148dac..e7db8af 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ *.out coverage.txt test-results.json +*.breakdown # Dependency directories (remove the comment below to include it) # vendor/ diff --git a/Makefile b/Makefile index b8a379a..220b40e 100644 --- a/Makefile +++ b/Makefile @@ -14,7 +14,7 @@ build: fmtcheck clean: @echo "==> Cleaning out old builds " go clean - rm -rf coverage.txt test-results.json + rm -rf coverage.txt test-results.json v2.breakdown fmt: From 1145f91ad40db9c50f7dba5124d26b08be9f9bbc Mon Sep 17 00:00:00 2001 From: Jaideep Padhye Date: Fri, 7 Aug 2026 16:50:34 -0700 Subject: [PATCH 04/13] SYN-6889: summary-only test report, rename coverage PR comment header Switch dorny/test-reporter to only-summary so the check/step summary shows pass/fail/skip counts without enumerating every individual test. Rename the coverage PR comment header to "Code Coverage Report". --- .github/workflows/ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 945ccd7..14e773f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -43,6 +43,7 @@ jobs: name: Go Tests path: test-results.json reporter: golang-json + only-summary: true # Fetch v2's (the base branch's) last coverage breakdown so PR runs can # report how this change moves coverage, not just its absolute value. - name: Download base branch coverage breakdown @@ -78,7 +79,7 @@ jobs: with: comment-tag: coverage-report message: | - go-test-coverage report: + Code Coverage Report: ``` ${{ fromJSON(steps.coverage.outputs.report) }}``` - name: Fail if coverage threshold not met From 51f3fe57856aa37298f27f70c8d3375ae8844d1d Mon Sep 17 00:00:00 2001 From: Jaideep Padhye Date: Fri, 7 Aug 2026 21:18:25 -0700 Subject: [PATCH 05/13] SYN-6889: add error-path unit tests, raise coverage threshold to 90 Fills the syntheticsclientv2 coverage gaps identified in SYN-6889's baseline analysis: connection-failure, malformed-response, empty-body, and non-2xx-status branches across the CRUD clients, plus the redaction/query-param helpers in synthetics.go and get_checksv2.go. Coverage moves from 73% to 94.4%, so the CI gate's ratchet floor is raised from 73 to 90 (the ticket's target) rather than left as a regression-only guard. Co-Authored-By: Claude --- .testcoverage.yml | 11 +- syntheticsclientv2/common_models_test.go | 54 ++++ syntheticsclientv2/create_apicheckv2_test.go | 51 ++++ .../create_browsercheckv2_test.go | 51 ++++ .../create_cacertificatev2_test.go | 47 ++++ .../create_clientcertificatev2_test.go | 45 ++++ .../create_downtimeconfigurationv2_test.go | 29 +++ syntheticsclientv2/create_httpcheckv2_test.go | 96 ++++++++ syntheticsclientv2/create_locationv2_test.go | 29 +++ syntheticsclientv2/create_portcheckv2_test.go | 47 ++++ syntheticsclientv2/create_sslcheckv2_test.go | 48 ++++ syntheticsclientv2/create_variablev2_test.go | 44 ++++ syntheticsclientv2/delete_apicheckv2_test.go | 36 +++ .../delete_browsercheckv2_test.go | 36 +++ .../delete_cacertificatev2_test.go | 33 +++ .../delete_clientcertificatev2_test.go | 33 +++ .../delete_downtimeconfigurationv2_test.go | 30 +++ syntheticsclientv2/delete_httpcheckv2_test.go | 36 +++ syntheticsclientv2/delete_locationv2_test.go | 30 +++ syntheticsclientv2/delete_portcheckv2_test.go | 34 +++ syntheticsclientv2/delete_sslcheckv2_test.go | 34 +++ syntheticsclientv2/delete_variablesv2_test.go | 31 +++ syntheticsclientv2/get_apicheckv2_test.go | 41 ++++ syntheticsclientv2/get_browsercheckv2_test.go | 41 ++++ .../get_cacertificatesv2_test.go | 37 +++ .../get_cacertificatev2_test.go | 37 +++ syntheticsclientv2/get_checksv2_test.go | 98 ++++++++ .../get_clientcertificatesv2_test.go | 37 +++ .../get_clientcertificatev2_test.go | 37 +++ syntheticsclientv2/get_devicesv2_test.go | 29 +++ .../get_downtimeconfigurationsv2_test.go | 29 +++ .../get_excludedfiletypesv2_test.go | 39 +++ syntheticsclientv2/get_httpcheckv2_test.go | 82 +++++++ syntheticsclientv2/get_locationsv2_test.go | 58 +++++ syntheticsclientv2/get_portcheckv2_test.go | 37 +++ syntheticsclientv2/get_sslcheckv2_test.go | 36 +++ syntheticsclientv2/get_variablev2_test.go | 60 +++++ syntheticsclientv2/synthetics_test.go | 172 +++++++++++++ syntheticsclientv2/totpvariablev2_test.go | 230 ++++++++++++++++++ syntheticsclientv2/update_apicheckv2_test.go | 78 ++++++ .../update_browsercheckv2_test.go | 78 ++++++ .../update_cacertificatev2_test.go | 72 ++++++ .../update_clientcertificatev2_test.go | 69 ++++++ .../update_downtimeconfigurationv2_test.go | 50 ++++ syntheticsclientv2/update_httpcheckv2_test.go | 147 +++++++++++ syntheticsclientv2/update_portcheckv2_test.go | 76 ++++++ syntheticsclientv2/update_sslcheckv2_test.go | 47 ++++ syntheticsclientv2/update_variablesv2_test.go | 71 ++++++ 48 files changed, 2668 insertions(+), 5 deletions(-) create mode 100644 syntheticsclientv2/common_models_test.go diff --git a/.testcoverage.yml b/.testcoverage.yml index e5a51f7..4e7b47b 100644 --- a/.testcoverage.yml +++ b/.testcoverage.yml @@ -1,8 +1,9 @@ profile: coverage.txt threshold: - # Ratchet floor at today's syntheticsclientv2 baseline. syntheticsclient (v1) - # is deprecated and excluded from tests/coverage entirely (see Makefile). - # Raise to 90 once SYN-6889's test coverage work lands; until then this only - # guards against regressions. - total: 73 + # SYN-6889 added error-path unit tests, raising syntheticsclientv2 coverage + # from 73% to 94.4%. Floor set at 90 (the ticket's target) rather than the + # measured value, to leave headroom for minor fluctuations without + # relaxing the original goal. syntheticsclient (v1) is deprecated and + # excluded from tests/coverage entirely (see Makefile). + total: 90 diff --git a/syntheticsclientv2/common_models_test.go b/syntheticsclientv2/common_models_test.go new file mode 100644 index 0000000..b50cedd --- /dev/null +++ b/syntheticsclientv2/common_models_test.go @@ -0,0 +1,54 @@ +//go:build unit_tests +// +build unit_tests + +// Copyright 2026 Splunk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package syntheticsclientv2 + +import ( + "encoding/json" + "testing" +) + +func TestNewNullableString(t *testing.T) { + n := NewNullableString("hello") + if n.Value == nil || *n.Value != "hello" { + t.Errorf("returned \n\n%#v want value \n\n%#v", n.Value, "hello") + } + + body, err := json.Marshal(n) + if err != nil { + t.Fatal(err) + } + if string(body) != `"hello"` { + t.Errorf("returned \n\n%#v want \n\n%#v", string(body), `"hello"`) + } +} + +func TestNullableStringUnmarshalJSONError(t *testing.T) { + var n NullableString + err := n.UnmarshalJSON([]byte(`123`)) + if err == nil { + t.Fatal("expected an error unmarshalling a non-string value into NullableString") + } +} + +func TestNullableIntUnmarshalJSONError(t *testing.T) { + var n NullableInt + err := n.UnmarshalJSON([]byte(`"not-an-int"`)) + if err == nil { + t.Fatal("expected an error unmarshalling a non-numeric value into NullableInt") + } +} diff --git a/syntheticsclientv2/create_apicheckv2_test.go b/syntheticsclientv2/create_apicheckv2_test.go index a1b026c..f0e17ba 100644 --- a/syntheticsclientv2/create_apicheckv2_test.go +++ b/syntheticsclientv2/create_apicheckv2_test.go @@ -105,3 +105,54 @@ func TestCreateApiCheckV2(t *testing.T) { t.Errorf("returned \n\n%#v want \n\n%#v", resp.Test.Customproperties, inputData.Test.Customproperties) } } + +func TestCreateApiCheckV2ReturnsErrorOnMalformedResponse(t *testing.T) { + setup() + defer teardown() + + testMux.HandleFunc("/v2/tests/api", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + _, err := w.Write([]byte("{not valid json")) + if err != nil { + t.Fatal(err) + } + }) + + err := json.Unmarshal([]byte(createApiV2Body), &inputData) + if err != nil { + t.Fatal(err) + } + + resp, details, err := testClient.CreateApiCheckV2(&inputData) + + if err == nil { + t.Fatal("expected an error on malformed JSON response, but got none") + } + if resp != nil && resp.Test.Name != "" { + t.Error("expected empty response struct on parse error") + } + if details == nil { + t.Fatal("expected request details even on parse error") + } +} + +func TestCreateApiCheckV2ReturnsErrorWhenRequestFails(t *testing.T) { + unreachableClient := NewConfigurableClient("apiKey", "realm", ClientArgs{publicBaseUrl: "http://127.0.0.1:1"}) + + err := json.Unmarshal([]byte(createApiV2Body), &inputData) + if err != nil { + t.Fatal(err) + } + + resp, details, err := unreachableClient.CreateApiCheckV2(&inputData) + + if err == nil { + t.Fatal("expected a connection error, but got none") + } + if resp != nil { + t.Errorf("expected nil response on network error, but got %#v", resp) + } + if details == nil { + t.Fatal("expected request details to be populated") + } +} diff --git a/syntheticsclientv2/create_browsercheckv2_test.go b/syntheticsclientv2/create_browsercheckv2_test.go index 20fdc0b..e78f1dd 100644 --- a/syntheticsclientv2/create_browsercheckv2_test.go +++ b/syntheticsclientv2/create_browsercheckv2_test.go @@ -100,3 +100,54 @@ func TestCreateBrowserCheckV2(t *testing.T) { t.Errorf("returned \n\n%#v want \n\n%#v", resp.Test.Customproperties, inputBrowserCheckV2Data.Test.Customproperties) } } + +func TestCreateBrowserCheckV2ReturnsErrorOnMalformedResponse(t *testing.T) { + setup() + defer teardown() + + testMux.HandleFunc("/v2/tests/browser", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + _, err := w.Write([]byte("{not valid json")) + if err != nil { + t.Fatal(err) + } + }) + + err := json.Unmarshal([]byte(createBrowserCheckV2Body), &inputBrowserCheckV2Data) + if err != nil { + t.Fatal(err) + } + + resp, details, err := testClient.CreateBrowserCheckV2(&inputBrowserCheckV2Data) + + if err == nil { + t.Fatal("expected an error on malformed JSON response, but got none") + } + if resp != nil && resp.Test.Name != "" { + t.Error("expected empty response struct on parse error") + } + if details == nil { + t.Fatal("expected request details even on parse error") + } +} + +func TestCreateBrowserCheckV2ReturnsErrorWhenRequestFails(t *testing.T) { + unreachableClient := NewConfigurableClient("apiKey", "realm", ClientArgs{publicBaseUrl: "http://127.0.0.1:1"}) + + err := json.Unmarshal([]byte(createBrowserCheckV2Body), &inputBrowserCheckV2Data) + if err != nil { + t.Fatal(err) + } + + resp, details, err := unreachableClient.CreateBrowserCheckV2(&inputBrowserCheckV2Data) + + if err == nil { + t.Fatal("expected a connection error, but got none") + } + if resp != nil { + t.Errorf("expected nil response on network error, but got %#v", resp) + } + if details == nil { + t.Fatal("expected request details to be populated") + } +} diff --git a/syntheticsclientv2/create_cacertificatev2_test.go b/syntheticsclientv2/create_cacertificatev2_test.go index 8c8c512..8b254b0 100644 --- a/syntheticsclientv2/create_cacertificatev2_test.go +++ b/syntheticsclientv2/create_cacertificatev2_test.go @@ -22,6 +22,7 @@ import ( "io" "net/http" "reflect" + "strings" "testing" ) @@ -122,3 +123,49 @@ func TestCreateCaCertificateV2(t *testing.T) { t.Errorf("returned \n\n%#v want \n\n%#v", resp.CaCert, outputCaCertificateV2Data.CaCert) } } + +func TestCreateCaCertificateV2ReturnsErrorOnMalformedResponse(t *testing.T) { + setup() + defer teardown() + + testMux.HandleFunc("/cacerts", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + _, err := w.Write([]byte("{not valid json")) + if err != nil { + t.Fatal(err) + } + }) + + err := json.Unmarshal([]byte(createCaCertificateV2Body), &inputCaCertificateV2Data) + if err != nil { + t.Fatal(err) + } + + resp, _, err := testClient.CreateCaCertificateV2(&inputCaCertificateV2Data) + + if err == nil { + t.Fatal("expected error on malformed JSON response, got nil") + } + if resp != nil { + t.Errorf("expected nil response on error, got %#v", resp) + } + if !strings.Contains(err.Error(), "invalid character") { + t.Errorf("expected JSON unmarshal error, got: %v", err) + } +} + +func TestCreateCaCertificateV2ReturnsErrorWhenRequestFails(t *testing.T) { + // Use an unreachable address to trigger a connection error + unreachableClient := NewConfigurableClient("apiKey", "realm", ClientArgs{publicBaseUrl: "http://127.0.0.1:1"}) + + err := json.Unmarshal([]byte(createCaCertificateV2Body), &inputCaCertificateV2Data) + if err != nil { + t.Fatal(err) + } + + _, _, err = unreachableClient.CreateCaCertificateV2(&inputCaCertificateV2Data) + + if err == nil { + t.Fatal("expected connection error, got nil") + } +} diff --git a/syntheticsclientv2/create_clientcertificatev2_test.go b/syntheticsclientv2/create_clientcertificatev2_test.go index cb12f16..0876986 100644 --- a/syntheticsclientv2/create_clientcertificatev2_test.go +++ b/syntheticsclientv2/create_clientcertificatev2_test.go @@ -22,6 +22,7 @@ import ( "io" "net/http" "reflect" + "strings" "testing" ) @@ -114,3 +115,47 @@ func readClientCertificateRequestFields(t *testing.T, r *http.Request) ([]byte, return requestBody, requestCertificateFields } + +func TestCreateClientCertificateV2ReturnsErrorOnMalformedResponse(t *testing.T) { + setup() + defer teardown() + + testMux.HandleFunc("/certificates", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + _, err := w.Write([]byte("{not valid json")) + if err != nil { + t.Fatal(err) + } + }) + + if err := json.Unmarshal([]byte(createClientCertificateV2Body), &inputClientCertificateV2Data); err != nil { + t.Fatal(err) + } + + resp, _, err := testClient.CreateClientCertificateV2(&inputClientCertificateV2Data) + + if err == nil { + t.Fatal("expected error on malformed JSON response, got nil") + } + if resp != nil { + t.Errorf("expected nil response on error, got %#v", resp) + } + if !strings.Contains(err.Error(), "invalid character") { + t.Errorf("expected JSON unmarshal error, got: %v", err) + } +} + +func TestCreateClientCertificateV2ReturnsErrorWhenRequestFails(t *testing.T) { + // Use an unreachable address to trigger a connection error + unreachableClient := NewConfigurableClient("apiKey", "realm", ClientArgs{publicBaseUrl: "http://127.0.0.1:1"}) + + if err := json.Unmarshal([]byte(createClientCertificateV2Body), &inputClientCertificateV2Data); err != nil { + t.Fatal(err) + } + + _, _, err := unreachableClient.CreateClientCertificateV2(&inputClientCertificateV2Data) + + if err == nil { + t.Fatal("expected connection error, got nil") + } +} diff --git a/syntheticsclientv2/create_downtimeconfigurationv2_test.go b/syntheticsclientv2/create_downtimeconfigurationv2_test.go index f368633..bd6940a 100644 --- a/syntheticsclientv2/create_downtimeconfigurationv2_test.go +++ b/syntheticsclientv2/create_downtimeconfigurationv2_test.go @@ -88,3 +88,32 @@ func TestCreateDowntimeConfigurationV2(t *testing.T) { } } + +func TestCreateDowntimeConfigurationV2ReturnsErrorOnMalformedResponse(t *testing.T) { + setup() + defer teardown() + + testMux.HandleFunc("/downtime_configurations", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + w.Write([]byte("{not valid json")) + }) + + resp, details, err := testClient.CreateDowntimeConfigurationV2(&DowntimeConfigurationV2Input{}) + if err == nil { + t.Fatal("expected a parse error, but got none") + } + if details == nil { + t.Fatal("expected request details") + } + if resp != nil { + t.Errorf("expected nil response, got %#v", resp) + } +} + +func TestCreateDowntimeConfigurationV2ReturnsErrorOnNetworkFailure(t *testing.T) { + unreachableClient := NewConfigurableClient("apiKey", "realm", ClientArgs{publicBaseUrl: "http://127.0.0.1:1"}) + _, _, err := unreachableClient.CreateDowntimeConfigurationV2(&DowntimeConfigurationV2Input{}) + if err == nil { + t.Fatal("expected a connection error") + } +} diff --git a/syntheticsclientv2/create_httpcheckv2_test.go b/syntheticsclientv2/create_httpcheckv2_test.go index a47f736..36d5fee 100644 --- a/syntheticsclientv2/create_httpcheckv2_test.go +++ b/syntheticsclientv2/create_httpcheckv2_test.go @@ -101,3 +101,99 @@ func TestCreateHttpCheckV2(t *testing.T) { t.Errorf("returned \n\n%#v want \n\n%#v", resp.Test.Port, inputHttpCheckV2Data.Test.Port) } } + +func TestCreateHttpCheckV2ReturnsErrorOnMalformedResponse(t *testing.T) { + setup() + defer teardown() + + testMux.HandleFunc("/tests/http", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + _, err := w.Write([]byte("{not valid json")) + if err != nil { + t.Fatal(err) + } + }) + + err := json.Unmarshal([]byte(createHttpCheckV2Body), &inputHttpCheckV2Data) + if err != nil { + t.Fatal(err) + } + + resp, details, err := testClient.CreateHttpCheckV2(&inputHttpCheckV2Data) + + if err == nil { + t.Fatal("expected an error on malformed JSON response, but got none") + } + if resp != nil && resp.Test.Name != "" { + t.Error("expected empty response struct on parse error") + } + if details == nil { + t.Fatal("expected request details even on parse error") + } +} + +func TestCreateHttpCheckV2ReturnsErrorWhenRequestFails(t *testing.T) { + unreachableClient := NewConfigurableClient("apiKey", "realm", ClientArgs{publicBaseUrl: "http://127.0.0.1:1"}) + + err := json.Unmarshal([]byte(createHttpCheckV2Body), &inputHttpCheckV2Data) + if err != nil { + t.Fatal(err) + } + + resp, details, err := unreachableClient.CreateHttpCheckV2(&inputHttpCheckV2Data) + + if err == nil { + t.Fatal("expected a connection error, but got none") + } + if resp != nil { + t.Errorf("expected nil response on network error, but got %#v", resp) + } + if details == nil { + t.Fatal("expected request details to be populated") + } +} + +func TestCreateHttpCheckV2WithNullablePortReturnsErrorOnMalformedResponse(t *testing.T) { + setup() + defer teardown() + + testMux.HandleFunc("/tests/http", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + _, err := w.Write([]byte("{not valid json")) + if err != nil { + t.Fatal(err) + } + }) + + inputWithNullablePort := HttpCheckV2InputWithNullablePort{} + inputWithNullablePort.Test.Name = "test-http" + resp, details, err := testClient.CreateHttpCheckV2WithNullablePort(&inputWithNullablePort) + + if err == nil { + t.Fatal("expected an error on malformed JSON response, but got none") + } + if resp != nil && resp.Test.Name != "" { + t.Error("expected empty response struct on parse error") + } + if details == nil { + t.Fatal("expected request details even on parse error") + } +} + +func TestCreateHttpCheckV2WithNullablePortReturnsErrorWhenRequestFails(t *testing.T) { + unreachableClient := NewConfigurableClient("apiKey", "realm", ClientArgs{publicBaseUrl: "http://127.0.0.1:1"}) + + inputWithNullablePort := HttpCheckV2InputWithNullablePort{} + inputWithNullablePort.Test.Name = "test-http" + resp, details, err := unreachableClient.CreateHttpCheckV2WithNullablePort(&inputWithNullablePort) + + if err == nil { + t.Fatal("expected a connection error, but got none") + } + if resp != nil { + t.Errorf("expected nil response on network error, but got %#v", resp) + } + if details == nil { + t.Fatal("expected request details to be populated") + } +} diff --git a/syntheticsclientv2/create_locationv2_test.go b/syntheticsclientv2/create_locationv2_test.go index e297611..e64df6a 100644 --- a/syntheticsclientv2/create_locationv2_test.go +++ b/syntheticsclientv2/create_locationv2_test.go @@ -64,3 +64,32 @@ func TestCreateLocationV2(t *testing.T) { } } + +func TestCreateLocationV2ReturnsErrorOnMalformedResponse(t *testing.T) { + setup() + defer teardown() + + testMux.HandleFunc("/locations", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + w.Write([]byte("{not valid json")) + }) + + resp, details, err := testClient.CreateLocationV2(&LocationV2Input{}) + if err == nil { + t.Fatal("expected a parse error, but got none") + } + if details == nil { + t.Fatal("expected request details") + } + if resp != nil { + t.Errorf("expected nil response, got %#v", resp) + } +} + +func TestCreateLocationV2ReturnsErrorOnNetworkFailure(t *testing.T) { + unreachableClient := NewConfigurableClient("apiKey", "realm", ClientArgs{publicBaseUrl: "http://127.0.0.1:1"}) + _, _, err := unreachableClient.CreateLocationV2(&LocationV2Input{}) + if err == nil { + t.Fatal("expected a connection error") + } +} diff --git a/syntheticsclientv2/create_portcheckv2_test.go b/syntheticsclientv2/create_portcheckv2_test.go index 22c63b6..f1eeaef 100644 --- a/syntheticsclientv2/create_portcheckv2_test.go +++ b/syntheticsclientv2/create_portcheckv2_test.go @@ -22,6 +22,7 @@ import ( "fmt" "net/http" "reflect" + "strings" "testing" ) @@ -87,3 +88,49 @@ func TestCreatePortCheckV2(t *testing.T) { t.Errorf("returned \n\n%#v want \n\n%#v", resp.Test.Customproperties, inputPortCheckV2Data.Test.Customproperties) } } + +func TestCreatePortCheckV2ReturnsErrorOnMalformedResponse(t *testing.T) { + setup() + defer teardown() + + testMux.HandleFunc("/tests/port", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + _, err := w.Write([]byte("{not valid json")) + if err != nil { + t.Fatal(err) + } + }) + + err := json.Unmarshal([]byte(createPortCheckV2Body), &inputPortCheckV2Data) + if err != nil { + t.Fatal(err) + } + + resp, _, err := testClient.CreatePortCheckV2(&inputPortCheckV2Data) + + if err == nil { + t.Fatal("expected error on malformed JSON response, got nil") + } + if resp != nil { + t.Errorf("expected nil response on error, got %#v", resp) + } + if !strings.Contains(err.Error(), "invalid character") { + t.Errorf("expected JSON unmarshal error, got: %v", err) + } +} + +func TestCreatePortCheckV2ReturnsErrorWhenRequestFails(t *testing.T) { + // Use an unreachable address to trigger a connection error + unreachableClient := NewConfigurableClient("apiKey", "realm", ClientArgs{publicBaseUrl: "http://127.0.0.1:1"}) + + err := json.Unmarshal([]byte(createPortCheckV2Body), &inputPortCheckV2Data) + if err != nil { + t.Fatal(err) + } + + _, _, err = unreachableClient.CreatePortCheckV2(&inputPortCheckV2Data) + + if err == nil { + t.Fatal("expected connection error, got nil") + } +} diff --git a/syntheticsclientv2/create_sslcheckv2_test.go b/syntheticsclientv2/create_sslcheckv2_test.go index 679d0ce..44eab1c 100644 --- a/syntheticsclientv2/create_sslcheckv2_test.go +++ b/syntheticsclientv2/create_sslcheckv2_test.go @@ -170,6 +170,54 @@ func assertStringPtr(t *testing.T, got *string, want string) { } } +func TestCreateSslCheckV2ReturnsErrorOnMalformedResponse(t *testing.T) { + setup() + defer teardown() + + testMux.HandleFunc("/tests/ssl", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + _, err := w.Write([]byte("{not valid json")) + if err != nil { + t.Fatal(err) + } + }) + + inputSslCheckV2Data := SslCheckV2Input{} + err := json.Unmarshal([]byte(createSslCheckV2Body), &inputSslCheckV2Data) + if err != nil { + t.Fatal(err) + } + + resp, _, err := testClient.CreateSslCheckV2(&inputSslCheckV2Data) + + if err == nil { + t.Fatal("expected error on malformed JSON response, got nil") + } + if resp != nil { + t.Errorf("expected nil response on error, got %#v", resp) + } + if !strings.Contains(err.Error(), "invalid character") { + t.Errorf("expected JSON unmarshal error, got: %v", err) + } +} + +func TestCreateSslCheckV2ReturnsErrorWhenRequestFails(t *testing.T) { + // Use an unreachable address to trigger a connection error + unreachableClient := NewConfigurableClient("apiKey", "realm", ClientArgs{publicBaseUrl: "http://127.0.0.1:1"}) + + inputSslCheckV2Data := SslCheckV2Input{} + err := json.Unmarshal([]byte(createSslCheckV2Body), &inputSslCheckV2Data) + if err != nil { + t.Fatal(err) + } + + _, _, err = unreachableClient.CreateSslCheckV2(&inputSslCheckV2Data) + + if err == nil { + t.Fatal("expected connection error, got nil") + } +} + func TestCreateSslCheckV2DefaultsNilValidations(t *testing.T) { setup() defer teardown() diff --git a/syntheticsclientv2/create_variablev2_test.go b/syntheticsclientv2/create_variablev2_test.go index 5964019..e39d984 100644 --- a/syntheticsclientv2/create_variablev2_test.go +++ b/syntheticsclientv2/create_variablev2_test.go @@ -76,3 +76,47 @@ func TestCreateVariableV2(t *testing.T) { } } + +func TestCreateVariableV2ReturnsErrorOnMalformedResponse(t *testing.T) { + setup() + defer teardown() + + err := json.Unmarshal([]byte(createVariableV2Body), &inputVariableV2Data) + if err != nil { + t.Fatal(err) + } + + testMux.HandleFunc("/variables", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + _, err := w.Write([]byte("{not valid json")) + if err != nil { + t.Fatal(err) + } + }) + + resp, _, err := testClient.CreateVariableV2(&inputVariableV2Data) + if err == nil { + t.Fatal("expected error on malformed response, got nil") + } + if resp != nil { + t.Errorf("expected nil response on parse error, got %#v", resp) + } +} + +func TestCreateVariableV2ReturnsErrorOnNetworkFailure(t *testing.T) { + unreachableClient := NewConfigurableClient("apiKey", "realm", ClientArgs{publicBaseUrl: "http://127.0.0.1:1"}) + + inputData := VariableV2Input{ + Variable: Variable{ + Name: "test-var", + Value: "test-value", + Secret: false, + Description: "test description", + }, + } + + _, _, err := unreachableClient.CreateVariableV2(&inputData) + if err == nil { + t.Fatal("expected connection error, got nil") + } +} diff --git a/syntheticsclientv2/delete_apicheckv2_test.go b/syntheticsclientv2/delete_apicheckv2_test.go index 7cae14d..ee16055 100644 --- a/syntheticsclientv2/delete_apicheckv2_test.go +++ b/syntheticsclientv2/delete_apicheckv2_test.go @@ -20,6 +20,7 @@ package syntheticsclientv2 import ( "fmt" "net/http" + "strings" "testing" ) @@ -46,3 +47,38 @@ func TestDeleteApiCheckV2(t *testing.T) { } fmt.Println(resp) } + +func TestDeleteApiCheckV2ReturnsErrorOnNon2xxStatus(t *testing.T) { + setup() + defer teardown() + + testMux.HandleFunc("/v2/tests/api/20", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + w.WriteHeader(http.StatusInternalServerError) + }) + + status, err := testClient.DeleteApiCheckV2(20) + + if err == nil { + t.Fatal("expected an error on non-2xx status, but got none") + } + if !strings.Contains(err.Error(), "unknown error, status code") { + t.Fatalf("expected error message to contain 'unknown error, status code', but got: %s", err.Error()) + } + if status != 1 { + t.Errorf("expected status 1 on API error, but got %d", status) + } +} + +func TestDeleteApiCheckV2ReturnsErrorWhenRequestFails(t *testing.T) { + unreachableClient := NewConfigurableClient("apiKey", "realm", ClientArgs{publicBaseUrl: "http://127.0.0.1:1"}) + + status, err := unreachableClient.DeleteApiCheckV2(21) + + if err == nil { + t.Fatal("expected a connection error, but got none") + } + if status != 1 { + t.Errorf("expected status 1 on network error, but got %d", status) + } +} diff --git a/syntheticsclientv2/delete_browsercheckv2_test.go b/syntheticsclientv2/delete_browsercheckv2_test.go index 3b724b9..2a9718b 100644 --- a/syntheticsclientv2/delete_browsercheckv2_test.go +++ b/syntheticsclientv2/delete_browsercheckv2_test.go @@ -20,6 +20,7 @@ package syntheticsclientv2 import ( "fmt" "net/http" + "strings" "testing" ) @@ -46,3 +47,38 @@ func TestDeleteBrowserCheckV2(t *testing.T) { } fmt.Println(resp) } + +func TestDeleteBrowserCheckV2ReturnsErrorOnNon2xxStatus(t *testing.T) { + setup() + defer teardown() + + testMux.HandleFunc("/v2/tests/browser/20", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + w.WriteHeader(http.StatusNotFound) + }) + + status, err := testClient.DeleteBrowserCheckV2(20) + + if err == nil { + t.Fatal("expected an error on non-2xx status, but got none") + } + if !strings.Contains(err.Error(), "unknown error, status code") { + t.Fatalf("expected error message to contain 'unknown error, status code', but got: %s", err.Error()) + } + if status != 1 { + t.Errorf("expected status 1 on API error, but got %d", status) + } +} + +func TestDeleteBrowserCheckV2ReturnsErrorWhenRequestFails(t *testing.T) { + unreachableClient := NewConfigurableClient("apiKey", "realm", ClientArgs{publicBaseUrl: "http://127.0.0.1:1"}) + + status, err := unreachableClient.DeleteBrowserCheckV2(21) + + if err == nil { + t.Fatal("expected a connection error, but got none") + } + if status != 1 { + t.Errorf("expected status 1 on network error, but got %d", status) + } +} diff --git a/syntheticsclientv2/delete_cacertificatev2_test.go b/syntheticsclientv2/delete_cacertificatev2_test.go index 9c2a625..fc25ed9 100644 --- a/syntheticsclientv2/delete_cacertificatev2_test.go +++ b/syntheticsclientv2/delete_cacertificatev2_test.go @@ -19,6 +19,7 @@ package syntheticsclientv2 import ( "net/http" + "strings" "testing" ) @@ -39,3 +40,35 @@ func TestDeleteCaCertificateV2(t *testing.T) { t.Errorf("returned \n\n%#v want \n\n%#v", resp, http.StatusNoContent) } } + +func TestDeleteCaCertificateV2ReturnsErrorOnNon2xxStatus(t *testing.T) { + setup() + defer teardown() + + testMux.HandleFunc("/cacerts/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + w.WriteHeader(http.StatusMultipleChoices) + }) + + resp, err := testClient.DeleteCaCertificateV2(1) + if err == nil { + t.Fatal("expected error on non-2xx status, got nil") + } + if resp != http.StatusMultipleChoices { + t.Errorf("returned status \n\n%#v want \n\n%#v", resp, http.StatusMultipleChoices) + } + if !strings.Contains(err.Error(), "Response code") { + t.Errorf("expected error message containing 'Response code', got: %v", err) + } +} + +func TestDeleteCaCertificateV2ReturnsErrorWhenRequestFails(t *testing.T) { + // Use an unreachable address to trigger a connection error + unreachableClient := NewConfigurableClient("apiKey", "realm", ClientArgs{publicBaseUrl: "http://127.0.0.1:1"}) + + _, err := unreachableClient.DeleteCaCertificateV2(1) + + if err == nil { + t.Fatal("expected connection error, got nil") + } +} diff --git a/syntheticsclientv2/delete_clientcertificatev2_test.go b/syntheticsclientv2/delete_clientcertificatev2_test.go index 96ff4be..1086b26 100644 --- a/syntheticsclientv2/delete_clientcertificatev2_test.go +++ b/syntheticsclientv2/delete_clientcertificatev2_test.go @@ -19,6 +19,7 @@ package syntheticsclientv2 import ( "net/http" + "strings" "testing" ) @@ -39,3 +40,35 @@ func TestDeleteClientCertificateV2(t *testing.T) { t.Errorf("returned \n\n%#v want \n\n%#v", resp, http.StatusNoContent) } } + +func TestDeleteClientCertificateV2ReturnsErrorOnNon2xxStatus(t *testing.T) { + setup() + defer teardown() + + testMux.HandleFunc("/certificates/123", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + w.WriteHeader(http.StatusMultipleChoices) + }) + + resp, err := testClient.DeleteClientCertificateV2(123) + if err == nil { + t.Fatal("expected error on non-2xx status, got nil") + } + if resp != http.StatusMultipleChoices { + t.Errorf("returned status \n\n%#v want \n\n%#v", resp, http.StatusMultipleChoices) + } + if !strings.Contains(err.Error(), "Response code") { + t.Errorf("expected error message containing 'Response code', got: %v", err) + } +} + +func TestDeleteClientCertificateV2ReturnsErrorWhenRequestFails(t *testing.T) { + // Use an unreachable address to trigger a connection error + unreachableClient := NewConfigurableClient("apiKey", "realm", ClientArgs{publicBaseUrl: "http://127.0.0.1:1"}) + + _, err := unreachableClient.DeleteClientCertificateV2(123) + + if err == nil { + t.Fatal("expected connection error, got nil") + } +} diff --git a/syntheticsclientv2/delete_downtimeconfigurationv2_test.go b/syntheticsclientv2/delete_downtimeconfigurationv2_test.go index b96ad92..f1c66a0 100644 --- a/syntheticsclientv2/delete_downtimeconfigurationv2_test.go +++ b/syntheticsclientv2/delete_downtimeconfigurationv2_test.go @@ -20,6 +20,7 @@ package syntheticsclientv2 import ( "fmt" "net/http" + "strings" "testing" ) @@ -46,3 +47,32 @@ func TestDeleteDowntimeConfigurationV2(t *testing.T) { } fmt.Println(resp) } + +func TestDeleteDowntimeConfigurationV2ReturnsErrorOnNetworkFailure(t *testing.T) { + unreachableClient := NewConfigurableClient("apiKey", "realm", ClientArgs{publicBaseUrl: "http://127.0.0.1:1"}) + _, err := unreachableClient.DeleteDowntimeConfigurationV2(19) + if err == nil { + t.Fatal("expected a connection error") + } +} + +func TestDeleteDowntimeConfigurationV2ReturnsErrorOnNon2xxStatus(t *testing.T) { + setup() + defer teardown() + + testMux.HandleFunc("/downtime_configurations/19", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + w.WriteHeader(http.StatusMultipleChoices) + }) + + resp, err := testClient.DeleteDowntimeConfigurationV2(19) + if err == nil { + t.Fatalf("expected an error for non-2xx status, but got none") + } + if !strings.Contains(err.Error(), "Response code") { + t.Errorf("expected error message to contain 'Response code', got: %v", err) + } + if resp != http.StatusMultipleChoices { + t.Errorf("expected status code %d, got %d", http.StatusMultipleChoices, resp) + } +} diff --git a/syntheticsclientv2/delete_httpcheckv2_test.go b/syntheticsclientv2/delete_httpcheckv2_test.go index 8e6221b..f104a7d 100644 --- a/syntheticsclientv2/delete_httpcheckv2_test.go +++ b/syntheticsclientv2/delete_httpcheckv2_test.go @@ -20,6 +20,7 @@ package syntheticsclientv2 import ( "fmt" "net/http" + "strings" "testing" ) @@ -46,3 +47,38 @@ func TestDeleteHttpCheckV2(t *testing.T) { } fmt.Println(resp) } + +func TestDeleteHttpCheckV2ReturnsErrorOnNon2xxStatus(t *testing.T) { + setup() + defer teardown() + + testMux.HandleFunc("/tests/http/20", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + w.WriteHeader(http.StatusBadRequest) + }) + + status, err := testClient.DeleteHttpCheckV2(20) + + if err == nil { + t.Fatal("expected an error on non-2xx status, but got none") + } + if !strings.Contains(err.Error(), "unknown error, status code") { + t.Fatalf("expected error message to contain 'unknown error, status code', but got: %s", err.Error()) + } + if status != 1 { + t.Errorf("expected status 1 on API error, but got %d", status) + } +} + +func TestDeleteHttpCheckV2ReturnsErrorWhenRequestFails(t *testing.T) { + unreachableClient := NewConfigurableClient("apiKey", "realm", ClientArgs{publicBaseUrl: "http://127.0.0.1:1"}) + + status, err := unreachableClient.DeleteHttpCheckV2(21) + + if err == nil { + t.Fatal("expected a connection error, but got none") + } + if status != 1 { + t.Errorf("expected status 1 on network error, but got %d", status) + } +} diff --git a/syntheticsclientv2/delete_locationv2_test.go b/syntheticsclientv2/delete_locationv2_test.go index e6fbf93..bcb05ee 100644 --- a/syntheticsclientv2/delete_locationv2_test.go +++ b/syntheticsclientv2/delete_locationv2_test.go @@ -20,6 +20,7 @@ package syntheticsclientv2 import ( "fmt" "net/http" + "strings" "testing" ) @@ -46,3 +47,32 @@ func TestDeleteLocationV2(t *testing.T) { } fmt.Println(resp) } + +func TestDeleteLocationV2ReturnsErrorOnNetworkFailure(t *testing.T) { + unreachableClient := NewConfigurableClient("apiKey", "realm", ClientArgs{publicBaseUrl: "http://127.0.0.1:1"}) + _, err := unreachableClient.DeleteLocationV2("beep") + if err == nil { + t.Fatal("expected a connection error") + } +} + +func TestDeleteLocationV2ReturnsErrorOnNon2xxStatus(t *testing.T) { + setup() + defer teardown() + + testMux.HandleFunc("/locations/beep", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + w.WriteHeader(http.StatusMultipleChoices) + }) + + resp, err := testClient.DeleteLocationV2("beep") + if err == nil { + t.Fatalf("expected an error for non-2xx status, but got none") + } + if !strings.Contains(err.Error(), "Response code") { + t.Errorf("expected error message to contain 'Response code', got: %v", err) + } + if resp != http.StatusMultipleChoices { + t.Errorf("expected status code %d, got %d", http.StatusMultipleChoices, resp) + } +} diff --git a/syntheticsclientv2/delete_portcheckv2_test.go b/syntheticsclientv2/delete_portcheckv2_test.go index 00d1d24..1789355 100644 --- a/syntheticsclientv2/delete_portcheckv2_test.go +++ b/syntheticsclientv2/delete_portcheckv2_test.go @@ -20,6 +20,7 @@ package syntheticsclientv2 import ( "fmt" "net/http" + "strings" "testing" ) @@ -46,3 +47,36 @@ func TestDeletePortCheckV2(t *testing.T) { } fmt.Println(resp) } + +func TestDeletePortCheckV2ReturnsErrorOnNon2xxStatus(t *testing.T) { + setup() + defer teardown() + + testMux.HandleFunc("/tests/port/19", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + w.WriteHeader(http.StatusMultipleChoices) + }) + + resp, err := testClient.DeletePortCheckV2(19) + + if err == nil { + t.Fatal("expected error on non-2xx response, got nil") + } + if !strings.Contains(err.Error(), "Response code") { + t.Errorf("expected 'Response code' in error message, got: %v", err) + } + if resp != http.StatusMultipleChoices { + t.Errorf("expected status code %d, got %d", http.StatusMultipleChoices, resp) + } +} + +func TestDeletePortCheckV2ReturnsErrorWhenRequestFails(t *testing.T) { + // Use an unreachable address to trigger a connection error + unreachableClient := NewConfigurableClient("apiKey", "realm", ClientArgs{publicBaseUrl: "http://127.0.0.1:1"}) + + _, err := unreachableClient.DeletePortCheckV2(19) + + if err == nil { + t.Fatal("expected connection error, got nil") + } +} diff --git a/syntheticsclientv2/delete_sslcheckv2_test.go b/syntheticsclientv2/delete_sslcheckv2_test.go index b7d6911..b9b83d9 100644 --- a/syntheticsclientv2/delete_sslcheckv2_test.go +++ b/syntheticsclientv2/delete_sslcheckv2_test.go @@ -19,6 +19,7 @@ package syntheticsclientv2 import ( "net/http" + "strings" "testing" ) @@ -39,3 +40,36 @@ func TestDeleteSslCheckV2(t *testing.T) { t.Errorf("returned \n\n%#v want \n\n%#v", resp, http.StatusNoContent) } } + +func TestDeleteSslCheckV2ReturnsErrorOnNon2xxStatus(t *testing.T) { + setup() + defer teardown() + + testMux.HandleFunc("/tests/ssl/19", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + w.WriteHeader(http.StatusMultipleChoices) + }) + + resp, err := testClient.DeleteSslCheckV2(19) + + if err == nil { + t.Fatal("expected error on non-2xx response, got nil") + } + if !strings.Contains(err.Error(), "Response code") { + t.Errorf("expected 'Response code' in error message, got: %v", err) + } + if resp != http.StatusMultipleChoices { + t.Errorf("expected status code %d, got %d", http.StatusMultipleChoices, resp) + } +} + +func TestDeleteSslCheckV2ReturnsErrorWhenRequestFails(t *testing.T) { + // Use an unreachable address to trigger a connection error + unreachableClient := NewConfigurableClient("apiKey", "realm", ClientArgs{publicBaseUrl: "http://127.0.0.1:1"}) + + _, err := unreachableClient.DeleteSslCheckV2(19) + + if err == nil { + t.Fatal("expected connection error, got nil") + } +} diff --git a/syntheticsclientv2/delete_variablesv2_test.go b/syntheticsclientv2/delete_variablesv2_test.go index 29ac73d..9011a5b 100644 --- a/syntheticsclientv2/delete_variablesv2_test.go +++ b/syntheticsclientv2/delete_variablesv2_test.go @@ -20,6 +20,7 @@ package syntheticsclientv2 import ( "fmt" "net/http" + "strings" "testing" ) @@ -46,3 +47,33 @@ func TestDeleteVariableV2(t *testing.T) { } fmt.Println(resp) } + +func TestDeleteVariableV2ReturnsErrorOnNetworkFailure(t *testing.T) { + unreachableClient := NewConfigurableClient("apiKey", "realm", ClientArgs{publicBaseUrl: "http://127.0.0.1:1"}) + + _, err := unreachableClient.DeleteVariableV2(19) + if err == nil { + t.Fatal("expected connection error, got nil") + } +} + +func TestDeleteVariableV2ReturnsErrorOnNon2xxStatus(t *testing.T) { + setup() + defer teardown() + + testMux.HandleFunc("/variables/20", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + w.WriteHeader(http.StatusMultipleChoices) + }) + + resp, err := testClient.DeleteVariableV2(20) + if err == nil { + t.Fatal("expected error on non-2xx status code, got nil") + } + if resp != http.StatusMultipleChoices { + t.Errorf("returned status \n\n%#v want \n\n%#v", resp, http.StatusMultipleChoices) + } + if !strings.Contains(err.Error(), "Response code") { + t.Errorf("expected error to contain 'Response code', got %s", err.Error()) + } +} diff --git a/syntheticsclientv2/get_apicheckv2_test.go b/syntheticsclientv2/get_apicheckv2_test.go index 499fa84..6e81957 100644 --- a/syntheticsclientv2/get_apicheckv2_test.go +++ b/syntheticsclientv2/get_apicheckv2_test.go @@ -95,3 +95,44 @@ func verifyApiCheckV2Input(stringInput string) *ApiCheckV2Response { } return check } + +func TestGetApiCheckV2ReturnsErrorOnMalformedResponse(t *testing.T) { + setup() + defer teardown() + + testMux.HandleFunc("/v2/tests/api/490", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + _, err := w.Write([]byte("{not valid json")) + if err != nil { + t.Fatal(err) + } + }) + + resp, details, err := testClient.GetApiCheckV2(490) + + if err == nil { + t.Fatal("expected an error on malformed JSON response, but got none") + } + if resp != nil && resp.Test.Name != "" { + t.Error("expected empty response struct on parse error") + } + if details == nil { + t.Fatal("expected request details even on parse error") + } +} + +func TestGetApiCheckV2ReturnsErrorWhenRequestFails(t *testing.T) { + unreachableClient := NewConfigurableClient("apiKey", "realm", ClientArgs{publicBaseUrl: "http://127.0.0.1:1"}) + + resp, details, err := unreachableClient.GetApiCheckV2(491) + + if err == nil { + t.Fatal("expected a connection error, but got none") + } + if resp != nil { + t.Errorf("expected nil response on network error, but got %#v", resp) + } + if details == nil { + t.Fatal("expected request details to be populated") + } +} diff --git a/syntheticsclientv2/get_browsercheckv2_test.go b/syntheticsclientv2/get_browsercheckv2_test.go index 779430e..32d99ee 100644 --- a/syntheticsclientv2/get_browsercheckv2_test.go +++ b/syntheticsclientv2/get_browsercheckv2_test.go @@ -171,3 +171,44 @@ func verifyBrowserCheckV2Input(stringInput string) *BrowserCheckV2Response { } return check } + +func TestGetBrowserCheckV2ReturnsErrorOnMalformedResponse(t *testing.T) { + setup() + defer teardown() + + testMux.HandleFunc("/v2/tests/browser/2", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + _, err := w.Write([]byte("{not valid json")) + if err != nil { + t.Fatal(err) + } + }) + + resp, details, err := testClient.GetBrowserCheckV2(2) + + if err == nil { + t.Fatal("expected an error on malformed JSON response, but got none") + } + if resp != nil && resp.Test.Name != "" { + t.Error("expected empty response struct on parse error") + } + if details == nil { + t.Fatal("expected request details even on parse error") + } +} + +func TestGetBrowserCheckV2ReturnsErrorWhenRequestFails(t *testing.T) { + unreachableClient := NewConfigurableClient("apiKey", "realm", ClientArgs{publicBaseUrl: "http://127.0.0.1:1"}) + + resp, details, err := unreachableClient.GetBrowserCheckV2(3) + + if err == nil { + t.Fatal("expected a connection error, but got none") + } + if resp != nil { + t.Errorf("expected nil response on network error, but got %#v", resp) + } + if details == nil { + t.Fatal("expected request details to be populated") + } +} diff --git a/syntheticsclientv2/get_cacertificatesv2_test.go b/syntheticsclientv2/get_cacertificatesv2_test.go index 5b30388..b8d8602 100644 --- a/syntheticsclientv2/get_cacertificatesv2_test.go +++ b/syntheticsclientv2/get_cacertificatesv2_test.go @@ -21,6 +21,7 @@ import ( "encoding/json" "net/http" "reflect" + "strings" "testing" ) @@ -59,3 +60,39 @@ func verifyCaCertificatesV2Input(stringInput string) *CaCertificatesV2Response { } return check } + +func TestGetCaCertificatesV2ReturnsErrorOnMalformedResponse(t *testing.T) { + setup() + defer teardown() + + testMux.HandleFunc("/cacerts", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + _, err := w.Write([]byte("{not valid json")) + if err != nil { + t.Fatal(err) + } + }) + + resp, _, err := testClient.GetCaCertificatesV2() + + if err == nil { + t.Fatal("expected error on malformed JSON response, got nil") + } + if resp != nil { + t.Errorf("expected nil response on error, got %#v", resp) + } + if !strings.Contains(err.Error(), "invalid character") { + t.Errorf("expected JSON unmarshal error, got: %v", err) + } +} + +func TestGetCaCertificatesV2ReturnsErrorWhenRequestFails(t *testing.T) { + // Use an unreachable address to trigger a connection error + unreachableClient := NewConfigurableClient("apiKey", "realm", ClientArgs{publicBaseUrl: "http://127.0.0.1:1"}) + + _, _, err := unreachableClient.GetCaCertificatesV2() + + if err == nil { + t.Fatal("expected connection error, got nil") + } +} diff --git a/syntheticsclientv2/get_cacertificatev2_test.go b/syntheticsclientv2/get_cacertificatev2_test.go index c3fdc5a..1239163 100644 --- a/syntheticsclientv2/get_cacertificatev2_test.go +++ b/syntheticsclientv2/get_cacertificatev2_test.go @@ -21,6 +21,7 @@ import ( "encoding/json" "net/http" "reflect" + "strings" "testing" ) @@ -59,3 +60,39 @@ func verifyCaCertificateV2Input(stringInput string) *CaCertificateV2Response { } return check } + +func TestGetCaCertificateV2ReturnsErrorOnMalformedResponse(t *testing.T) { + setup() + defer teardown() + + testMux.HandleFunc("/cacerts/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + _, err := w.Write([]byte("{not valid json")) + if err != nil { + t.Fatal(err) + } + }) + + resp, _, err := testClient.GetCaCertificateV2(1) + + if err == nil { + t.Fatal("expected error on malformed JSON response, got nil") + } + if resp != nil { + t.Errorf("expected nil response on error, got %#v", resp) + } + if !strings.Contains(err.Error(), "invalid character") { + t.Errorf("expected JSON unmarshal error, got: %v", err) + } +} + +func TestGetCaCertificateV2ReturnsErrorWhenRequestFails(t *testing.T) { + // Use an unreachable address to trigger a connection error + unreachableClient := NewConfigurableClient("apiKey", "realm", ClientArgs{publicBaseUrl: "http://127.0.0.1:1"}) + + _, _, err := unreachableClient.GetCaCertificateV2(1) + + if err == nil { + t.Fatal("expected connection error, got nil") + } +} diff --git a/syntheticsclientv2/get_checksv2_test.go b/syntheticsclientv2/get_checksv2_test.go index de819d9..5e45298 100644 --- a/syntheticsclientv2/get_checksv2_test.go +++ b/syntheticsclientv2/get_checksv2_test.go @@ -21,6 +21,7 @@ import ( "encoding/json" "net/http" "reflect" + "strings" "testing" ) @@ -67,3 +68,100 @@ func verifyChecksV2Input(stringInput string) *GetChecksV2Options { } return check } + +func TestGetChecksV2WithAllQueryParamsPopulated(t *testing.T) { + setup() + defer teardown() + + var gotQuery string + testMux.HandleFunc("/tests", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + gotQuery = r.URL.RawQuery + _, err := w.Write([]byte(`{"tests":[]}`)) + if err != nil { + t.Fatal(err) + } + }) + + active := true + params := &GetChecksV2Options{ + TestType: "api", + Page: 2, + PerPage: 25, + OrderBy: "id", + Search: "beep", + Active: &active, + SchedulingStrategy: "round_robin", + CustomProperties: []CustomProperties{{Key: "env", Value: "prod"}}, + LastRunStatus: []string{"success", "pending"}, + LocationIds: []string{"aws-us-east-1", "aws-ap-northeast-1"}, + TestTypes: []string{"api", "browser"}, + Frequencies: []int{5, 10}, + } + + _, _, err := testClient.GetChecksV2(params) + if err != nil { + t.Fatal(err) + } + + for _, want := range []string{ + "active=true", + "customProperties%5B%5D=env%3Aprod", + "lastRunStatus%5B%5D=success", + "lastRunStatus%5B%5D=pending", + "locationIds%5B%5D=aws-us-east-1", + "locationIds%5B%5D=aws-ap-northeast-1", + "testTypes%5B%5D=api", + "testTypes%5B%5D=browser", + "frequencies%5B%5D=5", + "frequencies%5B%5D=10", + } { + if !strings.Contains(gotQuery, want) { + t.Errorf("query %q missing expected substring %q", gotQuery, want) + } + } +} + +func TestActiveQueryParam(t *testing.T) { + active := true + if got, want := activeQueryParam(&active), "&active=true"; got != want { + t.Errorf("returned \n\n%#v want \n\n%#v", got, want) + } + if got, want := activeQueryParam(nil), ""; got != want { + t.Errorf("returned \n\n%#v want \n\n%#v", got, want) + } +} + +func TestCustomPropsQueryParam(t *testing.T) { + params := []CustomProperties{{Key: "env", Value: "prod"}, {Key: "team", Value: "synthetics"}} + got := customPropsQueryParam(params) + want := "&customProperties[]=env:prod&customProperties[]=team:synthetics" + if got != want { + t.Errorf("returned \n\n%#v want \n\n%#v", got, want) + } + if got := customPropsQueryParam(nil); got != "" { + t.Errorf("returned \n\n%#v want empty string", got) + } +} + +func TestIntegersQueryParam(t *testing.T) { + got := integersQueryParam([]int{5, 10}, "&frequencies[]=") + want := "&frequencies[]=5&frequencies[]=10" + if got != want { + t.Errorf("returned \n\n%#v want \n\n%#v", got, want) + } + if got := integersQueryParam(nil, "&frequencies[]="); got != "" { + t.Errorf("returned \n\n%#v want empty string", got) + } +} + +func TestStringsQueryParam(t *testing.T) { + got := stringsQueryParam([]string{"success", "pending"}, "&lastRunStatus[]=") + want := "&lastRunStatus[]=success&lastRunStatus[]=pending" + if got != want { + t.Errorf("returned \n\n%#v want \n\n%#v", got, want) + } + if got := stringsQueryParam(nil, "&lastRunStatus[]="); got != "" { + t.Errorf("returned \n\n%#v want empty string", got) + } +} diff --git a/syntheticsclientv2/get_clientcertificatesv2_test.go b/syntheticsclientv2/get_clientcertificatesv2_test.go index 1a7d27c..fed4b86 100644 --- a/syntheticsclientv2/get_clientcertificatesv2_test.go +++ b/syntheticsclientv2/get_clientcertificatesv2_test.go @@ -21,6 +21,7 @@ import ( "encoding/json" "net/http" "reflect" + "strings" "testing" ) @@ -58,3 +59,39 @@ func verifyClientCertificatesV2Input(stringInput string) *ClientCertificatesV2Re } return check } + +func TestGetClientCertificatesV2ReturnsErrorOnMalformedResponse(t *testing.T) { + setup() + defer teardown() + + testMux.HandleFunc("/certificates", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + _, err := w.Write([]byte("{not valid json")) + if err != nil { + t.Fatal(err) + } + }) + + resp, _, err := testClient.GetClientCertificatesV2() + + if err == nil { + t.Fatal("expected error on malformed JSON response, got nil") + } + if resp != nil { + t.Errorf("expected nil response on error, got %#v", resp) + } + if !strings.Contains(err.Error(), "invalid character") { + t.Errorf("expected JSON unmarshal error, got: %v", err) + } +} + +func TestGetClientCertificatesV2ReturnsErrorWhenRequestFails(t *testing.T) { + // Use an unreachable address to trigger a connection error + unreachableClient := NewConfigurableClient("apiKey", "realm", ClientArgs{publicBaseUrl: "http://127.0.0.1:1"}) + + _, _, err := unreachableClient.GetClientCertificatesV2() + + if err == nil { + t.Fatal("expected connection error, got nil") + } +} diff --git a/syntheticsclientv2/get_clientcertificatev2_test.go b/syntheticsclientv2/get_clientcertificatev2_test.go index 8e3c370..5dda83e 100644 --- a/syntheticsclientv2/get_clientcertificatev2_test.go +++ b/syntheticsclientv2/get_clientcertificatev2_test.go @@ -21,6 +21,7 @@ import ( "encoding/json" "net/http" "reflect" + "strings" "testing" ) @@ -58,3 +59,39 @@ func verifyClientCertificateV2Input(stringInput string) *ClientCertificateV2Resp } return check } + +func TestGetClientCertificateV2ReturnsErrorOnMalformedResponse(t *testing.T) { + setup() + defer teardown() + + testMux.HandleFunc("/certificates/123", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + _, err := w.Write([]byte("{not valid json")) + if err != nil { + t.Fatal(err) + } + }) + + resp, _, err := testClient.GetClientCertificateV2(123) + + if err == nil { + t.Fatal("expected error on malformed JSON response, got nil") + } + if resp != nil { + t.Errorf("expected nil response on error, got %#v", resp) + } + if !strings.Contains(err.Error(), "invalid character") { + t.Errorf("expected JSON unmarshal error, got: %v", err) + } +} + +func TestGetClientCertificateV2ReturnsErrorWhenRequestFails(t *testing.T) { + // Use an unreachable address to trigger a connection error + unreachableClient := NewConfigurableClient("apiKey", "realm", ClientArgs{publicBaseUrl: "http://127.0.0.1:1"}) + + _, _, err := unreachableClient.GetClientCertificateV2(123) + + if err == nil { + t.Fatal("expected connection error, got nil") + } +} diff --git a/syntheticsclientv2/get_devicesv2_test.go b/syntheticsclientv2/get_devicesv2_test.go index 7e665dc..41ac39e 100644 --- a/syntheticsclientv2/get_devicesv2_test.go +++ b/syntheticsclientv2/get_devicesv2_test.go @@ -81,3 +81,32 @@ func verifyDevicesV2Input(stringInput string) *DevicesV2Response { } return check } + +func TestGetDevicesV2ReturnsErrorOnMalformedResponse(t *testing.T) { + setup() + defer teardown() + + testMux.HandleFunc("/devices", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + w.Write([]byte("{not valid json")) + }) + + resp, details, err := testClient.GetDevicesV2() + if err == nil { + t.Fatal("expected a parse error, but got none") + } + if details == nil { + t.Fatal("expected request details") + } + if resp != nil { + t.Errorf("expected nil response, got %#v", resp) + } +} + +func TestGetDevicesV2ReturnsErrorOnNetworkFailure(t *testing.T) { + unreachableClient := NewConfigurableClient("apiKey", "realm", ClientArgs{publicBaseUrl: "http://127.0.0.1:1"}) + _, _, err := unreachableClient.GetDevicesV2() + if err == nil { + t.Fatal("expected a connection error") + } +} diff --git a/syntheticsclientv2/get_downtimeconfigurationsv2_test.go b/syntheticsclientv2/get_downtimeconfigurationsv2_test.go index d9fe4c2..87d78b8 100644 --- a/syntheticsclientv2/get_downtimeconfigurationsv2_test.go +++ b/syntheticsclientv2/get_downtimeconfigurationsv2_test.go @@ -145,3 +145,32 @@ func verifyDowntimeConfigurationsV2Input(stringInput string) *GetDowntimeConfigu } return check } + +func TestGetDowntimeConfigurationsV2ReturnsErrorOnMalformedResponse(t *testing.T) { + setup() + defer teardown() + + testMux.HandleFunc("/downtime_configurations", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + w.Write([]byte("{not valid json")) + }) + + resp, details, err := testClient.GetDowntimeConfigurationsV2(&GetDowntimeConfigurationsV2Options{}) + if err == nil { + t.Fatal("expected a parse error, but got none") + } + if details == nil { + t.Fatal("expected request details") + } + if resp != nil { + t.Errorf("expected nil response, got %#v", resp) + } +} + +func TestGetDowntimeConfigurationsV2ReturnsErrorOnNetworkFailure(t *testing.T) { + unreachableClient := NewConfigurableClient("apiKey", "realm", ClientArgs{publicBaseUrl: "http://127.0.0.1:1"}) + _, _, err := unreachableClient.GetDowntimeConfigurationsV2(&GetDowntimeConfigurationsV2Options{}) + if err == nil { + t.Fatal("expected a connection error") + } +} diff --git a/syntheticsclientv2/get_excludedfiletypesv2_test.go b/syntheticsclientv2/get_excludedfiletypesv2_test.go index 5c31ebc..59c1da3 100644 --- a/syntheticsclientv2/get_excludedfiletypesv2_test.go +++ b/syntheticsclientv2/get_excludedfiletypesv2_test.go @@ -65,3 +65,42 @@ func TestParseExcludedFileTypesV2Response(t *testing.T) { t.Fatalf("ExcludedFileTypes = %#v, want %#v", resp.ExcludedFileTypes, expected) } } + +func TestGetExcludedFileTypesV2ReturnsErrorOnMalformedResponse(t *testing.T) { + setup() + defer teardown() + + testMux.HandleFunc("/excluded_file_types", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + w.Write([]byte("{not valid json")) + }) + + resp, details, err := testClient.GetExcludedFileTypesV2() + if err == nil { + t.Fatal("expected a parse error, but got none") + } + if details == nil { + t.Fatal("expected request details") + } + if resp != nil { + t.Errorf("expected nil response, got %#v", resp) + } +} + +func TestGetExcludedFileTypesV2ReturnsErrorOnNetworkFailure(t *testing.T) { + unreachableClient := NewConfigurableClient("apiKey", "realm", ClientArgs{publicBaseUrl: "http://127.0.0.1:1"}) + _, _, err := unreachableClient.GetExcludedFileTypesV2() + if err == nil { + t.Fatal("expected a connection error") + } +} + +func TestParseExcludedFileTypesV2ResponseReturnsErrorOnMalformedJSON(t *testing.T) { + resp, err := parseExcludedFileTypesV2Response("{not valid json") + if err == nil { + t.Fatal("expected a parse error, but got none") + } + if resp != nil { + t.Errorf("expected nil response, got %#v", resp) + } +} diff --git a/syntheticsclientv2/get_httpcheckv2_test.go b/syntheticsclientv2/get_httpcheckv2_test.go index fd1abfa..071005a 100644 --- a/syntheticsclientv2/get_httpcheckv2_test.go +++ b/syntheticsclientv2/get_httpcheckv2_test.go @@ -115,3 +115,85 @@ func verifyHttpCheckV2Input(stringInput string) *HttpCheckV2Response { } return check } + +func TestGetHttpCheckV2ReturnsErrorOnMalformedResponse(t *testing.T) { + setup() + defer teardown() + + testMux.HandleFunc("/tests/http/2", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + _, err := w.Write([]byte("{not valid json")) + if err != nil { + t.Fatal(err) + } + }) + + resp, details, err := testClient.GetHttpCheckV2(2) + + if err == nil { + t.Fatal("expected an error on malformed JSON response, but got none") + } + if resp != nil && resp.Test.Name != "" { + t.Error("expected empty response struct on parse error") + } + if details == nil { + t.Fatal("expected request details even on parse error") + } +} + +func TestGetHttpCheckV2ReturnsErrorWhenRequestFails(t *testing.T) { + unreachableClient := NewConfigurableClient("apiKey", "realm", ClientArgs{publicBaseUrl: "http://127.0.0.1:1"}) + + resp, details, err := unreachableClient.GetHttpCheckV2(3) + + if err == nil { + t.Fatal("expected a connection error, but got none") + } + if resp != nil { + t.Errorf("expected nil response on network error, but got %#v", resp) + } + if details == nil { + t.Fatal("expected request details to be populated") + } +} + +func TestGetHttpCheckV2WithNullablePortReturnsErrorOnMalformedResponse(t *testing.T) { + setup() + defer teardown() + + testMux.HandleFunc("/tests/http/4", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + _, err := w.Write([]byte("{not valid json")) + if err != nil { + t.Fatal(err) + } + }) + + resp, details, err := testClient.GetHttpCheckV2WithNullablePort(4) + + if err == nil { + t.Fatal("expected an error on malformed JSON response, but got none") + } + if resp != nil && resp.Test.Name != "" { + t.Error("expected empty response struct on parse error") + } + if details == nil { + t.Fatal("expected request details even on parse error") + } +} + +func TestGetHttpCheckV2WithNullablePortReturnsErrorWhenRequestFails(t *testing.T) { + unreachableClient := NewConfigurableClient("apiKey", "realm", ClientArgs{publicBaseUrl: "http://127.0.0.1:1"}) + + resp, details, err := unreachableClient.GetHttpCheckV2WithNullablePort(5) + + if err == nil { + t.Fatal("expected a connection error, but got none") + } + if resp != nil { + t.Errorf("expected nil response on network error, but got %#v", resp) + } + if details == nil { + t.Fatal("expected request details to be populated") + } +} diff --git a/syntheticsclientv2/get_locationsv2_test.go b/syntheticsclientv2/get_locationsv2_test.go index 436030e..bd57196 100644 --- a/syntheticsclientv2/get_locationsv2_test.go +++ b/syntheticsclientv2/get_locationsv2_test.go @@ -140,3 +140,61 @@ func verifyLocationV2Input(stringInput string) *LocationV2Response { } return check } + +func TestGetLocationsV2ReturnsErrorOnMalformedResponse(t *testing.T) { + setup() + defer teardown() + + testMux.HandleFunc("/locations/", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + w.Write([]byte("{not valid json")) + }) + + resp, details, err := testClient.GetLocationsV2() + if err == nil { + t.Fatal("expected a parse error, but got none") + } + if details == nil { + t.Fatal("expected request details") + } + if resp != nil { + t.Errorf("expected nil response, got %#v", resp) + } +} + +func TestGetLocationsV2ReturnsErrorOnNetworkFailure(t *testing.T) { + unreachableClient := NewConfigurableClient("apiKey", "realm", ClientArgs{publicBaseUrl: "http://127.0.0.1:1"}) + _, _, err := unreachableClient.GetLocationsV2() + if err == nil { + t.Fatal("expected a connection error") + } +} + +func TestGetLocationV2ReturnsErrorOnMalformedResponse(t *testing.T) { + setup() + defer teardown() + + testMux.HandleFunc("/locations/aws-us-east-1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + w.Write([]byte("{not valid json")) + }) + + resp, details, err := testClient.GetLocationV2("aws-us-east-1") + if err == nil { + t.Fatal("expected a parse error, but got none") + } + if details == nil { + t.Fatal("expected request details") + } + if resp != nil { + t.Errorf("expected nil response, got %#v", resp) + } +} + +func TestGetLocationV2ReturnsErrorOnNetworkFailure(t *testing.T) { + unreachableClient := NewConfigurableClient("apiKey", "realm", ClientArgs{publicBaseUrl: "http://127.0.0.1:1"}) + _, _, err := unreachableClient.GetLocationV2("aws-us-east-1") + if err == nil { + t.Fatal("expected a connection error") + } +} diff --git a/syntheticsclientv2/get_portcheckv2_test.go b/syntheticsclientv2/get_portcheckv2_test.go index 1811ffd..b227ad2 100644 --- a/syntheticsclientv2/get_portcheckv2_test.go +++ b/syntheticsclientv2/get_portcheckv2_test.go @@ -21,6 +21,7 @@ import ( "encoding/json" "net/http" "reflect" + "strings" "testing" ) @@ -99,3 +100,39 @@ func verifyPortCheckV2Input(stringInput string) *PortCheckV2Response { } return check } + +func TestGetPortCheckV2ReturnsErrorOnMalformedResponse(t *testing.T) { + setup() + defer teardown() + + testMux.HandleFunc("/tests/port/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + _, err := w.Write([]byte("{not valid json")) + if err != nil { + t.Fatal(err) + } + }) + + resp, _, err := testClient.GetPortCheckV2(1) + + if err == nil { + t.Fatal("expected error on malformed JSON response, got nil") + } + if resp != nil { + t.Errorf("expected nil response on error, got %#v", resp) + } + if !strings.Contains(err.Error(), "invalid character") { + t.Errorf("expected JSON unmarshal error, got: %v", err) + } +} + +func TestGetPortCheckV2ReturnsErrorWhenRequestFails(t *testing.T) { + // Use an unreachable address to trigger a connection error + unreachableClient := NewConfigurableClient("apiKey", "realm", ClientArgs{publicBaseUrl: "http://127.0.0.1:1"}) + + _, _, err := unreachableClient.GetPortCheckV2(1) + + if err == nil { + t.Fatal("expected connection error, got nil") + } +} diff --git a/syntheticsclientv2/get_sslcheckv2_test.go b/syntheticsclientv2/get_sslcheckv2_test.go index 0c9cee5..0a86dde 100644 --- a/syntheticsclientv2/get_sslcheckv2_test.go +++ b/syntheticsclientv2/get_sslcheckv2_test.go @@ -116,3 +116,39 @@ func verifySslCheckV2Input(stringInput string) *SslCheckV2Response { } return check } + +func TestGetSslCheckV2ReturnsErrorOnMalformedResponse(t *testing.T) { + setup() + defer teardown() + + testMux.HandleFunc("/tests/ssl/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + _, err := w.Write([]byte("{not valid json")) + if err != nil { + t.Fatal(err) + } + }) + + resp, _, err := testClient.GetSslCheckV2(1) + + if err == nil { + t.Fatal("expected error on malformed JSON response, got nil") + } + if resp != nil { + t.Errorf("expected nil response on error, got %#v", resp) + } + if !strings.Contains(err.Error(), "invalid character") { + t.Errorf("expected JSON unmarshal error, got: %v", err) + } +} + +func TestGetSslCheckV2ReturnsErrorWhenRequestFails(t *testing.T) { + // Use an unreachable address to trigger a connection error + unreachableClient := NewConfigurableClient("apiKey", "realm", ClientArgs{publicBaseUrl: "http://127.0.0.1:1"}) + + _, _, err := unreachableClient.GetSslCheckV2(1) + + if err == nil { + t.Fatal("expected connection error, got nil") + } +} diff --git a/syntheticsclientv2/get_variablev2_test.go b/syntheticsclientv2/get_variablev2_test.go index 5fc6b1b..afe7440 100644 --- a/syntheticsclientv2/get_variablev2_test.go +++ b/syntheticsclientv2/get_variablev2_test.go @@ -142,3 +142,63 @@ func verifyVariablesV2Input(stringInput string) *VariablesV2Response { } return check } + +func TestGetVariableV2ReturnsErrorOnMalformedResponse(t *testing.T) { + setup() + defer teardown() + + testMux.HandleFunc("/variables/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + _, err := w.Write([]byte("{invalid json}")) + if err != nil { + t.Fatal(err) + } + }) + + resp, _, err := testClient.GetVariableV2(1) + if err == nil { + t.Fatal("expected error on malformed response, got nil") + } + if resp != nil { + t.Errorf("expected nil response on parse error, got %#v", resp) + } +} + +func TestGetVariableV2ReturnsErrorOnNetworkFailure(t *testing.T) { + unreachableClient := NewConfigurableClient("apiKey", "realm", ClientArgs{publicBaseUrl: "http://127.0.0.1:1"}) + + _, _, err := unreachableClient.GetVariableV2(1) + if err == nil { + t.Fatal("expected connection error, got nil") + } +} + +func TestGetVariablesV2ReturnsErrorOnMalformedResponse(t *testing.T) { + setup() + defer teardown() + + testMux.HandleFunc("/variables", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + _, err := w.Write([]byte("{invalid json array}")) + if err != nil { + t.Fatal(err) + } + }) + + resp, _, err := testClient.GetVariablesV2() + if err == nil { + t.Fatal("expected error on malformed response, got nil") + } + if resp != nil { + t.Errorf("expected nil response on parse error, got %#v", resp) + } +} + +func TestGetVariablesV2ReturnsErrorOnNetworkFailure(t *testing.T) { + unreachableClient := NewConfigurableClient("apiKey", "realm", ClientArgs{publicBaseUrl: "http://127.0.0.1:1"}) + + _, _, err := unreachableClient.GetVariablesV2() + if err == nil { + t.Fatal("expected connection error, got nil") + } +} diff --git a/syntheticsclientv2/synthetics_test.go b/syntheticsclientv2/synthetics_test.go index 7fe4863..f1834c0 100644 --- a/syntheticsclientv2/synthetics_test.go +++ b/syntheticsclientv2/synthetics_test.go @@ -19,6 +19,7 @@ package syntheticsclientv2 import ( "bytes" + "errors" "log" "net/http" "net/http/httptest" @@ -378,6 +379,177 @@ func TestSanitizeRequestDumpRedactsURLQueryValues(t *testing.T) { } } +func TestClientString(t *testing.T) { + setup() + defer teardown() + + got := testClient.String() + want := "Splunk Synthetics Client: URL: " + testClient.publicBaseURL + " " + if got != want { + t.Errorf("returned \n\n%#v want \n\n%#v", got, want) + } +} + +func TestNewClientArgs(t *testing.T) { + args := NewClientArgs(45, "https://example.com") + if args.timeoutSeconds != 45 { + t.Errorf("returned \n\n%#v want \n\n%#v", args.timeoutSeconds, 45) + } + if args.publicBaseUrl != "https://example.com" { + t.Errorf("returned \n\n%#v want \n\n%#v", args.publicBaseUrl, "https://example.com") + } +} + +func TestNewClient(t *testing.T) { + client := NewClient("snakedonut", "us0") + if client.apiKey != "snakedonut" { + t.Errorf("returned \n\n%#v want \n\n%#v", client.apiKey, "snakedonut") + } + if client.realm != "us0" { + t.Errorf("returned \n\n%#v want \n\n%#v", client.realm, "us0") + } + if client.publicBaseURL != "https://api.us0.signalfx.com/v2/synthetics" { + t.Errorf("returned \n\n%#v want \n\n%#v", client.publicBaseURL, "https://api.us0.signalfx.com/v2/synthetics") + } + if client.GetHTTPClient().Timeout != 30*time.Second { + t.Errorf("returned \n\n%#v want \n\n%#v", client.GetHTTPClient().Timeout, 30*time.Second) + } +} + +func TestJsonPrint(t *testing.T) { + // JsonPrint only writes to stdout; this exercises both the success and + // marshal-error branches without asserting on stdout content. + JsonPrint(map[string]string{"key": "value"}) + JsonPrint(make(chan int)) +} + +func TestMakePublicAPICallReturnsUnknownErrorForNonJSONErrorBody(t *testing.T) { + testMux = http.NewServeMux() + testServer = httptest.NewServer(testMux) + defer testServer.Close() + + testMux.HandleFunc("/tests", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusInternalServerError) + }) + + testConfigurableClient := NewConfigurableClient("apiKey", "realm", ClientArgs{ + publicBaseUrl: testServer.URL, + }) + details, err := testConfigurableClient.makePublicAPICall("GET", "/tests", nil, nil) + + if err == nil { + t.Fatal("expected an error for a non-2xx response with an empty body") + } + if !strings.Contains(err.Error(), "unknown error, status code: 500") { + t.Errorf("returned \n\n%#v want error containing \n\n%#v", err.Error(), "unknown error, status code: 500") + } + if details.StatusCode != http.StatusInternalServerError { + t.Errorf("returned \n\n%#v want \n\n%#v", details.StatusCode, http.StatusInternalServerError) + } +} + +func TestMakePublicAPICallSetsQueryParams(t *testing.T) { + testMux = http.NewServeMux() + testServer = httptest.NewServer(testMux) + defer testServer.Close() + + testMux.HandleFunc("/tests", func(w http.ResponseWriter, r *http.Request) { + if got := r.URL.Query().Get("page"); got != "2" { + t.Errorf("returned query param \n\n%#v want \n\n%#v", got, "2") + } + w.WriteHeader(http.StatusOK) + w.Write([]byte(`{}`)) + }) + + testConfigurableClient := NewConfigurableClient("apiKey", "realm", ClientArgs{ + publicBaseUrl: testServer.URL, + }) + _, err := testConfigurableClient.makePublicAPICall("GET", "/tests", nil, map[string]string{"page": "2"}) + if err != nil { + t.Fatalf("expected no error, but saw: %s", err.Error()) + } +} + +func TestMakePublicAPICallReturnsErrorForInvalidMethod(t *testing.T) { + setup() + defer teardown() + + _, err := testClient.makePublicAPICall("IN VALID", "/tests", nil, nil) + if err == nil { + t.Fatal("expected an error for an invalid HTTP method") + } +} + +type erroringReader struct{} + +func (erroringReader) Read([]byte) (int, error) { + return 0, errors.New("simulated read error") +} + +func TestMakePublicAPICallReturnsErrorWhenRequestBodyFailsToRead(t *testing.T) { + setup() + defer teardown() + + _, err := testClient.makePublicAPICall("POST", "/tests", erroringReader{}, nil) + if err == nil { + t.Fatal("expected an error when the request body fails to read") + } +} + +func TestRedactRequestDumpURLQueryWithNoNewline(t *testing.T) { + requestLine := "GET /tests?token=secret HTTP/1.1" + got := redactRequestDumpURLQuery(requestLine) + want := "GET /tests?token=[REDACTED] HTTP/1.1" + if got != want { + t.Errorf("returned \n\n%#v want \n\n%#v", got, want) + } +} + +func TestRedactRequestLineURLQueryWithMalformedRequestLine(t *testing.T) { + requestLine := "malformed-request-line" + got := redactRequestLineURLQuery(requestLine) + if got != requestLine { + t.Errorf("returned \n\n%#v want \n\n%#v", got, requestLine) + } +} + +func TestSplitRequestDumpReturnsFalseWhenNoSeparatorFound(t *testing.T) { + headers, body, separator, ok := splitRequestDump("no separator here") + if ok { + t.Fatalf("expected ok=false, but saw headers=%q body=%q separator=%q", headers, body, separator) + } +} + +func TestIsSensitiveHeaderNameMatchesKnownHeaderExactly(t *testing.T) { + if !isSensitiveHeaderName("Authorization") { + t.Error("expected Authorization to be treated as a sensitive header name") + } +} + +func TestRedactURLQueryValuesWithFragmentAndEmptyParts(t *testing.T) { + got := redactURLQueryValues("https://example.com/path?a=1&&=orphan#fragment-only") + want := "https://example.com/path?a=[REDACTED]&&[REDACTED]#fragment-only" + if got != want { + t.Errorf("returned \n\n%#v want \n\n%#v", got, want) + } +} + +func TestRedactURLQueryValuesWithFragmentOnlyQuery(t *testing.T) { + got := redactURLQueryValues("https://example.com/path?#fragment-only") + want := "https://example.com/path?#fragment-only" + if got != want { + t.Errorf("returned \n\n%#v want \n\n%#v", got, want) + } +} + +func TestRedactURLUserinfoWithProtocolRelativeURL(t *testing.T) { + got := redactURLUserinfo("//user:pass@example.com/path") + want := "//[REDACTED]@example.com/path" + if got != want { + t.Errorf("returned \n\n%#v want \n\n%#v", got, want) + } +} + func TestMakePublicAPICallDoesNotExposeRawRequest(t *testing.T) { testMux = http.NewServeMux() testServer = httptest.NewServer(testMux) diff --git a/syntheticsclientv2/totpvariablev2_test.go b/syntheticsclientv2/totpvariablev2_test.go index 1fd788b..2aba71f 100644 --- a/syntheticsclientv2/totpvariablev2_test.go +++ b/syntheticsclientv2/totpvariablev2_test.go @@ -241,6 +241,236 @@ func TestDeleteTotpVariableV2(t *testing.T) { } } +func TestCreateTotpVariableV2ReturnsErrorOnMalformedResponse(t *testing.T) { + setup() + defer teardown() + + err := json.Unmarshal([]byte(createTotpVariableV2Body), &inputTotpVariableV2Data) + if err != nil { + t.Fatal(err) + } + + testMux.HandleFunc("/totps", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + _, err := w.Write([]byte("{not valid json")) + if err != nil { + t.Fatal(err) + } + }) + + resp, _, err := testClient.CreateTotpVariableV2(&inputTotpVariableV2Data) + if err == nil { + t.Fatal("expected error on malformed response, got nil") + } + if resp != nil { + t.Errorf("expected nil response on parse error, got %#v", resp) + } +} + +func TestCreateTotpVariableV2ReturnsErrorOnNetworkFailure(t *testing.T) { + unreachableClient := NewConfigurableClient("apiKey", "realm", ClientArgs{publicBaseUrl: "http://127.0.0.1:1"}) + + inputData := TotpVariableV2Input{ + Totp: TotpVariableInput{ + Name: "test-totp", + Secret: "test-secret", + Digits: 6, + Interval: 30, + HmacDigest: "SHA1", + }, + } + + _, _, err := unreachableClient.CreateTotpVariableV2(&inputData) + if err == nil { + t.Fatal("expected connection error, got nil") + } +} + +func TestGetTotpVariableV2ReturnsErrorOnMalformedResponse(t *testing.T) { + setup() + defer teardown() + + testMux.HandleFunc("/totps/102", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + _, err := w.Write([]byte("{invalid json}")) + if err != nil { + t.Fatal(err) + } + }) + + resp, _, err := testClient.GetTotpVariableV2(102) + if err == nil { + t.Fatal("expected error on malformed response, got nil") + } + if resp != nil { + t.Errorf("expected nil response on parse error, got %#v", resp) + } +} + +func TestGetTotpVariableV2ReturnsErrorOnNetworkFailure(t *testing.T) { + unreachableClient := NewConfigurableClient("apiKey", "realm", ClientArgs{publicBaseUrl: "http://127.0.0.1:1"}) + + _, _, err := unreachableClient.GetTotpVariableV2(102) + if err == nil { + t.Fatal("expected connection error, got nil") + } +} + +func TestGetTotpVariablesV2ReturnsErrorOnMalformedResponse(t *testing.T) { + setup() + defer teardown() + + testMux.HandleFunc("/totps", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + _, err := w.Write([]byte("{invalid json array}")) + if err != nil { + t.Fatal(err) + } + }) + + resp, _, err := testClient.GetTotpVariablesV2() + if err == nil { + t.Fatal("expected error on malformed response, got nil") + } + if resp != nil { + t.Errorf("expected nil response on parse error, got %#v", resp) + } +} + +func TestGetTotpVariablesV2ReturnsErrorOnNetworkFailure(t *testing.T) { + unreachableClient := NewConfigurableClient("apiKey", "realm", ClientArgs{publicBaseUrl: "http://127.0.0.1:1"}) + + _, _, err := unreachableClient.GetTotpVariablesV2() + if err == nil { + t.Fatal("expected connection error, got nil") + } +} + +func TestUpdateTotpVariableV2ReturnsErrorOnMalformedResponse(t *testing.T) { + setup() + defer teardown() + + description := "Updated TOTP" + digits := 8 + hmacDigest := "SHA512" + interval := 45 + secret := "update-totp-secret" + updateInput := TotpVariableV2UpdateInput{ + Totp: TotpVariableUpdateInput{ + Description: &description, + Digits: &digits, + HmacDigest: &hmacDigest, + Interval: &interval, + Secret: &secret, + }, + } + + testMux.HandleFunc("/totps/103", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + _, err := w.Write([]byte("{malformed response}")) + if err != nil { + t.Fatal(err) + } + }) + + resp, _, err := testClient.UpdateTotpVariableV2(103, &updateInput) + if err == nil { + t.Fatal("expected error on malformed response, got nil") + } + if resp != nil { + t.Errorf("expected nil response on parse error, got %#v", resp) + } +} + +func TestUpdateTotpVariableV2ReturnsErrorOnNetworkFailure(t *testing.T) { + unreachableClient := NewConfigurableClient("apiKey", "realm", ClientArgs{publicBaseUrl: "http://127.0.0.1:1"}) + + description := "Updated TOTP" + digits := 8 + hmacDigest := "SHA512" + interval := 45 + secret := "update-totp-secret" + updateInput := TotpVariableV2UpdateInput{ + Totp: TotpVariableUpdateInput{ + Description: &description, + Digits: &digits, + HmacDigest: &hmacDigest, + Interval: &interval, + Secret: &secret, + }, + } + + _, _, err := unreachableClient.UpdateTotpVariableV2(103, &updateInput) + if err == nil { + t.Fatal("expected connection error, got nil") + } +} + +func TestUpdateTotpVariableV2ReturnsNonNilResponseOnEmptyBody(t *testing.T) { + setup() + defer teardown() + + description := "Updated TOTP" + digits := 8 + hmacDigest := "SHA512" + interval := 45 + updateInput := TotpVariableV2UpdateInput{ + Totp: TotpVariableUpdateInput{ + Description: &description, + Digits: &digits, + HmacDigest: &hmacDigest, + Interval: &interval, + }, + } + + testMux.HandleFunc("/totps/106", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + w.WriteHeader(http.StatusOK) + _, err := w.Write([]byte("")) + if err != nil { + t.Fatal(err) + } + }) + + resp, _, err := testClient.UpdateTotpVariableV2(106, &updateInput) + if err != nil { + t.Fatal(err) + } + if resp == nil { + t.Fatal("expected non-nil response for empty body with 2xx status") + } +} + +func TestDeleteTotpVariableV2ReturnsErrorOnNetworkFailure(t *testing.T) { + unreachableClient := NewConfigurableClient("apiKey", "realm", ClientArgs{publicBaseUrl: "http://127.0.0.1:1"}) + + _, err := unreachableClient.DeleteTotpVariableV2(105) + if err == nil { + t.Fatal("expected connection error, got nil") + } +} + +func TestDeleteTotpVariableV2ReturnsErrorOnNon2xxStatus(t *testing.T) { + setup() + defer teardown() + + testMux.HandleFunc("/totps/107", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + w.WriteHeader(http.StatusMultipleChoices) + }) + + resp, err := testClient.DeleteTotpVariableV2(107) + if err == nil { + t.Fatal("expected error on non-2xx status code, got nil") + } + if resp != http.StatusMultipleChoices { + t.Errorf("returned status \n\n%#v want \n\n%#v", resp, http.StatusMultipleChoices) + } + if !strings.Contains(err.Error(), "Response code") { + t.Errorf("expected error to contain 'Response code', got %s", err.Error()) + } +} + func verifyTotpVariableV2Input(stringInput string) *TotpVariableV2Response { check := &TotpVariableV2Response{} err := json.Unmarshal([]byte(stringInput), check) diff --git a/syntheticsclientv2/update_apicheckv2_test.go b/syntheticsclientv2/update_apicheckv2_test.go index 2045fba..5f4fb10 100644 --- a/syntheticsclientv2/update_apicheckv2_test.go +++ b/syntheticsclientv2/update_apicheckv2_test.go @@ -82,3 +82,81 @@ func TestUpdateApiCheckV2(t *testing.T) { t.Errorf("returned \n\n%#v want \n\n%#v", resp.Test.Customproperties, inputApiCheckV2Update.Test.Customproperties) } } + +func TestUpdateApiCheckV2HandlesEmptyResponseBody(t *testing.T) { + setup() + defer teardown() + + testMux.HandleFunc("/v2/tests/api/10", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + w.WriteHeader(http.StatusNoContent) + }) + + err := json.Unmarshal([]byte(updateApiCheckV2Body), &inputApiCheckV2Update) + if err != nil { + t.Fatal(err) + } + + resp, details, err := testClient.UpdateApiCheckV2(10, &inputApiCheckV2Update) + + if err != nil { + t.Fatal(err) + } + if resp == nil { + t.Fatal("expected non-nil response on empty body") + } + if details == nil { + t.Fatal("expected request details") + } +} + +func TestUpdateApiCheckV2ReturnsErrorOnMalformedResponse(t *testing.T) { + setup() + defer teardown() + + testMux.HandleFunc("/v2/tests/api/10", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + _, err := w.Write([]byte("{not valid json")) + if err != nil { + t.Fatal(err) + } + }) + + err := json.Unmarshal([]byte(updateApiCheckV2Body), &inputApiCheckV2Update) + if err != nil { + t.Fatal(err) + } + + resp, details, err := testClient.UpdateApiCheckV2(10, &inputApiCheckV2Update) + + if err == nil { + t.Fatal("expected an error on malformed JSON response, but got none") + } + if resp != nil && resp.Test.Name != "" { + t.Error("expected empty response struct on parse error") + } + if details == nil { + t.Fatal("expected request details even on parse error") + } +} + +func TestUpdateApiCheckV2ReturnsErrorWhenRequestFails(t *testing.T) { + unreachableClient := NewConfigurableClient("apiKey", "realm", ClientArgs{publicBaseUrl: "http://127.0.0.1:1"}) + + err := json.Unmarshal([]byte(updateApiCheckV2Body), &inputApiCheckV2Update) + if err != nil { + t.Fatal(err) + } + + resp, details, err := unreachableClient.UpdateApiCheckV2(10, &inputApiCheckV2Update) + + if err == nil { + t.Fatal("expected a connection error, but got none") + } + if resp != nil { + t.Errorf("expected nil response on network error, but got %#v", resp) + } + if details == nil { + t.Fatal("expected request details to be populated") + } +} diff --git a/syntheticsclientv2/update_browsercheckv2_test.go b/syntheticsclientv2/update_browsercheckv2_test.go index 0173363..b88a380 100644 --- a/syntheticsclientv2/update_browsercheckv2_test.go +++ b/syntheticsclientv2/update_browsercheckv2_test.go @@ -302,3 +302,81 @@ func TestUpdateBrowserCheckV2(t *testing.T) { t.Errorf("returned \n\n%#v want \n\n%#v", resp.Test.Advancedsettings, inputBrowserCheckV2Update.Test.Advancedsettings) } } + +func TestUpdateBrowserCheckV2HandlesEmptyResponseBody(t *testing.T) { + setup() + defer teardown() + + testMux.HandleFunc("/v2/tests/browser/15", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + w.WriteHeader(http.StatusNoContent) + }) + + err := json.Unmarshal([]byte(updateBrowserCheckV2Body), &inputBrowserCheckV2Update) + if err != nil { + t.Fatal(err) + } + + resp, details, err := testClient.UpdateBrowserCheckV2(15, &inputBrowserCheckV2Update) + + if err != nil { + t.Fatal(err) + } + if resp == nil { + t.Fatal("expected non-nil response on empty body") + } + if details == nil { + t.Fatal("expected request details") + } +} + +func TestUpdateBrowserCheckV2ReturnsErrorOnMalformedResponse(t *testing.T) { + setup() + defer teardown() + + testMux.HandleFunc("/v2/tests/browser/16", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + _, err := w.Write([]byte("{not valid json")) + if err != nil { + t.Fatal(err) + } + }) + + err := json.Unmarshal([]byte(updateBrowserCheckV2Body), &inputBrowserCheckV2Update) + if err != nil { + t.Fatal(err) + } + + resp, details, err := testClient.UpdateBrowserCheckV2(16, &inputBrowserCheckV2Update) + + if err == nil { + t.Fatal("expected an error on malformed JSON response, but got none") + } + if resp != nil && resp.Test.Name != "" { + t.Error("expected empty response struct on parse error") + } + if details == nil { + t.Fatal("expected request details even on parse error") + } +} + +func TestUpdateBrowserCheckV2ReturnsErrorWhenRequestFails(t *testing.T) { + unreachableClient := NewConfigurableClient("apiKey", "realm", ClientArgs{publicBaseUrl: "http://127.0.0.1:1"}) + + err := json.Unmarshal([]byte(updateBrowserCheckV2Body), &inputBrowserCheckV2Update) + if err != nil { + t.Fatal(err) + } + + resp, details, err := unreachableClient.UpdateBrowserCheckV2(17, &inputBrowserCheckV2Update) + + if err == nil { + t.Fatal("expected a connection error, but got none") + } + if resp != nil { + t.Errorf("expected nil response on network error, but got %#v", resp) + } + if details == nil { + t.Fatal("expected request details to be populated") + } +} diff --git a/syntheticsclientv2/update_cacertificatev2_test.go b/syntheticsclientv2/update_cacertificatev2_test.go index c1b3522..61edc50 100644 --- a/syntheticsclientv2/update_cacertificatev2_test.go +++ b/syntheticsclientv2/update_cacertificatev2_test.go @@ -21,6 +21,7 @@ import ( "encoding/json" "io" "net/http" + "strings" "testing" ) @@ -176,3 +177,74 @@ func readCaCertificateUpdateRequestFields(t *testing.T, r *http.Request) ([]byte return requestBody, requestCaCertFields } + +func TestUpdateCaCertificateV2ReturnsErrorOnMalformedResponse(t *testing.T) { + setup() + defer teardown() + + testMux.HandleFunc("/cacerts/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + _, err := w.Write([]byte("{not valid json")) + if err != nil { + t.Fatal(err) + } + }) + + err := json.Unmarshal([]byte(updateCaCertificateV2Body), &inputCaCertificateV2Update) + if err != nil { + t.Fatal(err) + } + + resp, _, err := testClient.UpdateCaCertificateV2(1, &inputCaCertificateV2Update) + + if err == nil { + t.Fatal("expected error on malformed JSON response, got nil") + } + if resp != nil { + t.Errorf("expected nil response on error, got %#v", resp) + } + if !strings.Contains(err.Error(), "invalid character") { + t.Errorf("expected JSON unmarshal error, got: %v", err) + } +} + +func TestUpdateCaCertificateV2ReturnsErrorWhenRequestFails(t *testing.T) { + // Use an unreachable address to trigger a connection error + unreachableClient := NewConfigurableClient("apiKey", "realm", ClientArgs{publicBaseUrl: "http://127.0.0.1:1"}) + + err := json.Unmarshal([]byte(updateCaCertificateV2Body), &inputCaCertificateV2Update) + if err != nil { + t.Fatal(err) + } + + _, _, err = unreachableClient.UpdateCaCertificateV2(1, &inputCaCertificateV2Update) + + if err == nil { + t.Fatal("expected connection error, got nil") + } +} + +func TestUpdateCaCertificateV2HandlesEmptyResponseBody(t *testing.T) { + setup() + defer teardown() + + testMux.HandleFunc("/cacerts/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + w.WriteHeader(http.StatusOK) + // Write empty response body + }) + + err := json.Unmarshal([]byte(updateCaCertificateV2Body), &inputCaCertificateV2Update) + if err != nil { + t.Fatal(err) + } + + resp, _, err := testClient.UpdateCaCertificateV2(1, &inputCaCertificateV2Update) + + if err != nil { + t.Fatal(err) + } + if resp == nil { + t.Fatal("expected non-nil response for empty body") + } +} diff --git a/syntheticsclientv2/update_clientcertificatev2_test.go b/syntheticsclientv2/update_clientcertificatev2_test.go index 92fcc20..efd4a96 100644 --- a/syntheticsclientv2/update_clientcertificatev2_test.go +++ b/syntheticsclientv2/update_clientcertificatev2_test.go @@ -20,6 +20,7 @@ package syntheticsclientv2 import ( "encoding/json" "net/http" + "strings" "testing" ) @@ -107,3 +108,71 @@ func assertClientCertificateUpdateRequestBody(t *testing.T, r *http.Request) { } } } + +func TestUpdateClientCertificateV2ReturnsErrorOnMalformedResponse(t *testing.T) { + setup() + defer teardown() + + testMux.HandleFunc("/certificates/123", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + _, err := w.Write([]byte("{not valid json")) + if err != nil { + t.Fatal(err) + } + }) + + if err := json.Unmarshal([]byte(updateClientCertificateV2Body), &inputClientCertificateV2Update); err != nil { + t.Fatal(err) + } + + resp, _, err := testClient.UpdateClientCertificateV2(123, &inputClientCertificateV2Update) + + if err == nil { + t.Fatal("expected error on malformed JSON response, got nil") + } + if resp != nil { + t.Errorf("expected nil response on error, got %#v", resp) + } + if !strings.Contains(err.Error(), "invalid character") { + t.Errorf("expected JSON unmarshal error, got: %v", err) + } +} + +func TestUpdateClientCertificateV2ReturnsErrorWhenRequestFails(t *testing.T) { + // Use an unreachable address to trigger a connection error + unreachableClient := NewConfigurableClient("apiKey", "realm", ClientArgs{publicBaseUrl: "http://127.0.0.1:1"}) + + if err := json.Unmarshal([]byte(updateClientCertificateV2Body), &inputClientCertificateV2Update); err != nil { + t.Fatal(err) + } + + _, _, err := unreachableClient.UpdateClientCertificateV2(123, &inputClientCertificateV2Update) + + if err == nil { + t.Fatal("expected connection error, got nil") + } +} + +func TestUpdateClientCertificateV2HandlesEmptyResponseBody(t *testing.T) { + setup() + defer teardown() + + testMux.HandleFunc("/certificates/123", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + w.WriteHeader(http.StatusOK) + // Write empty response body + }) + + if err := json.Unmarshal([]byte(updateClientCertificateV2Body), &inputClientCertificateV2Update); err != nil { + t.Fatal(err) + } + + resp, _, err := testClient.UpdateClientCertificateV2(123, &inputClientCertificateV2Update) + + if err != nil { + t.Fatal(err) + } + if resp == nil { + t.Fatal("expected non-nil response for empty body") + } +} diff --git a/syntheticsclientv2/update_downtimeconfigurationv2_test.go b/syntheticsclientv2/update_downtimeconfigurationv2_test.go index 5e85801..7ff6fad 100644 --- a/syntheticsclientv2/update_downtimeconfigurationv2_test.go +++ b/syntheticsclientv2/update_downtimeconfigurationv2_test.go @@ -78,3 +78,53 @@ func TestUpdateDowntimeConfigurationV2(t *testing.T) { t.Errorf("returned \n\n%#v want \n\n%#v", resp.DowntimeConfiguration.Endtime, inputDowntimeConfigurationV2Update.DowntimeConfiguration.Endtime) } } + +func TestUpdateDowntimeConfigurationV2ReturnsErrorOnMalformedResponse(t *testing.T) { + setup() + defer teardown() + + testMux.HandleFunc("/downtime_configurations/10", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + w.Write([]byte("{not valid json")) + }) + + resp, details, err := testClient.UpdateDowntimeConfigurationV2(10, &DowntimeConfigurationV2Input{}) + if err == nil { + t.Fatal("expected a parse error, but got none") + } + if details == nil { + t.Fatal("expected request details") + } + if resp != nil { + t.Errorf("expected nil response, got %#v", resp) + } +} + +func TestUpdateDowntimeConfigurationV2ReturnsErrorOnNetworkFailure(t *testing.T) { + unreachableClient := NewConfigurableClient("apiKey", "realm", ClientArgs{publicBaseUrl: "http://127.0.0.1:1"}) + _, _, err := unreachableClient.UpdateDowntimeConfigurationV2(10, &DowntimeConfigurationV2Input{}) + if err == nil { + t.Fatal("expected a connection error") + } +} + +func TestUpdateDowntimeConfigurationV2ReturnsResponseOnEmptyBody(t *testing.T) { + setup() + defer teardown() + + testMux.HandleFunc("/downtime_configurations/10", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + w.WriteHeader(http.StatusOK) + }) + + resp, details, err := testClient.UpdateDowntimeConfigurationV2(10, &DowntimeConfigurationV2Input{}) + if err != nil { + t.Fatalf("expected no error for empty response, but got: %v", err) + } + if details == nil { + t.Fatal("expected request details") + } + if resp == nil { + t.Fatal("expected non-nil response for empty body") + } +} diff --git a/syntheticsclientv2/update_httpcheckv2_test.go b/syntheticsclientv2/update_httpcheckv2_test.go index 2cefbdc..730ffdb 100644 --- a/syntheticsclientv2/update_httpcheckv2_test.go +++ b/syntheticsclientv2/update_httpcheckv2_test.go @@ -66,3 +66,150 @@ func TestUpdateHttpCheckV2(t *testing.T) { t.Errorf("returned \n\n%#v want \n\n%#v", resp.Test.Port, inputHttpCheckV2Update.Test.Port) } } + +func TestUpdateHttpCheckV2HandlesEmptyResponseBody(t *testing.T) { + setup() + defer teardown() + + testMux.HandleFunc("/tests/http/11", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + w.WriteHeader(http.StatusNoContent) + }) + + err := json.Unmarshal([]byte(updateHttpCheckV2Body), &inputHttpCheckV2Update) + if err != nil { + t.Fatal(err) + } + + resp, details, err := testClient.UpdateHttpCheckV2(11, &inputHttpCheckV2Update) + + if err != nil { + t.Fatal(err) + } + if resp == nil { + t.Fatal("expected non-nil response on empty body") + } + if details == nil { + t.Fatal("expected request details") + } +} + +func TestUpdateHttpCheckV2ReturnsErrorOnMalformedResponse(t *testing.T) { + setup() + defer teardown() + + testMux.HandleFunc("/tests/http/12", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + _, err := w.Write([]byte("{not valid json")) + if err != nil { + t.Fatal(err) + } + }) + + err := json.Unmarshal([]byte(updateHttpCheckV2Body), &inputHttpCheckV2Update) + if err != nil { + t.Fatal(err) + } + + resp, details, err := testClient.UpdateHttpCheckV2(12, &inputHttpCheckV2Update) + + if err == nil { + t.Fatal("expected an error on malformed JSON response, but got none") + } + if resp != nil && resp.Test.Name != "" { + t.Error("expected empty response struct on parse error") + } + if details == nil { + t.Fatal("expected request details even on parse error") + } +} + +func TestUpdateHttpCheckV2ReturnsErrorWhenRequestFails(t *testing.T) { + unreachableClient := NewConfigurableClient("apiKey", "realm", ClientArgs{publicBaseUrl: "http://127.0.0.1:1"}) + + err := json.Unmarshal([]byte(updateHttpCheckV2Body), &inputHttpCheckV2Update) + if err != nil { + t.Fatal(err) + } + + resp, details, err := unreachableClient.UpdateHttpCheckV2(13, &inputHttpCheckV2Update) + + if err == nil { + t.Fatal("expected a connection error, but got none") + } + if resp != nil { + t.Errorf("expected nil response on network error, but got %#v", resp) + } + if details == nil { + t.Fatal("expected request details to be populated") + } +} + +func TestUpdateHttpCheckV2WithNullablePortReturnsErrorOnMalformedResponse(t *testing.T) { + setup() + defer teardown() + + testMux.HandleFunc("/tests/http/15", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + _, err := w.Write([]byte("{not valid json")) + if err != nil { + t.Fatal(err) + } + }) + + inputWithNullablePort := HttpCheckV2InputWithNullablePort{} + inputWithNullablePort.Test.Name = "test-http" + resp, details, err := testClient.UpdateHttpCheckV2WithNullablePort(15, &inputWithNullablePort) + + if err == nil { + t.Fatal("expected an error on malformed JSON response, but got none") + } + if resp != nil && resp.Test.Name != "" { + t.Error("expected empty response struct on parse error") + } + if details == nil { + t.Fatal("expected request details even on parse error") + } +} + +func TestUpdateHttpCheckV2WithNullablePortReturnsErrorWhenRequestFails(t *testing.T) { + unreachableClient := NewConfigurableClient("apiKey", "realm", ClientArgs{publicBaseUrl: "http://127.0.0.1:1"}) + + inputWithNullablePort := HttpCheckV2InputWithNullablePort{} + inputWithNullablePort.Test.Name = "test-http" + resp, details, err := unreachableClient.UpdateHttpCheckV2WithNullablePort(16, &inputWithNullablePort) + + if err == nil { + t.Fatal("expected a connection error, but got none") + } + if resp != nil { + t.Errorf("expected nil response on network error, but got %#v", resp) + } + if details == nil { + t.Fatal("expected request details to be populated") + } +} + +func TestUpdateHttpCheckV2WithNullablePortHandlesEmptyResponseBody(t *testing.T) { + setup() + defer teardown() + + testMux.HandleFunc("/tests/http/14", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + w.WriteHeader(http.StatusNoContent) + }) + + inputWithNullablePort := HttpCheckV2InputWithNullablePort{} + inputWithNullablePort.Test.Name = "test-http" + resp, details, err := testClient.UpdateHttpCheckV2WithNullablePort(14, &inputWithNullablePort) + + if err != nil { + t.Fatal(err) + } + if resp == nil { + t.Fatal("expected non-nil response on empty body") + } + if details == nil { + t.Fatal("expected request details") + } +} diff --git a/syntheticsclientv2/update_portcheckv2_test.go b/syntheticsclientv2/update_portcheckv2_test.go index 3ac2af2..620a602 100644 --- a/syntheticsclientv2/update_portcheckv2_test.go +++ b/syntheticsclientv2/update_portcheckv2_test.go @@ -22,6 +22,7 @@ import ( "fmt" "net/http" "reflect" + "strings" "testing" ) @@ -62,3 +63,78 @@ func TestUpdatePortCheckV2(t *testing.T) { t.Errorf("returned \n\n%#v want \n\n%#v", resp.Test.Customproperties, inputPortCheckV2Update.Test.Customproperties) } } + +func TestUpdatePortCheckV2HandlesEmptyResponseBody(t *testing.T) { + setup() + defer teardown() + + testMux.HandleFunc("/tests/port/1650", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + w.WriteHeader(http.StatusOK) + // Write empty body + _, err := w.Write([]byte("")) + if err != nil { + t.Fatal(err) + } + }) + + err := json.Unmarshal([]byte(updatePortCheckV2Body), &inputPortCheckV2Update) + if err != nil { + t.Fatal(err) + } + + resp, _, err := testClient.UpdatePortCheckV2(1650, &inputPortCheckV2Update) + + if err != nil { + t.Fatal(err) + } + if resp == nil { + t.Errorf("expected non-nil response on empty body, got nil") + } +} + +func TestUpdatePortCheckV2ReturnsErrorOnMalformedResponse(t *testing.T) { + setup() + defer teardown() + + testMux.HandleFunc("/tests/port/1650", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + _, err := w.Write([]byte("{not valid json")) + if err != nil { + t.Fatal(err) + } + }) + + err := json.Unmarshal([]byte(updatePortCheckV2Body), &inputPortCheckV2Update) + if err != nil { + t.Fatal(err) + } + + resp, _, err := testClient.UpdatePortCheckV2(1650, &inputPortCheckV2Update) + + if err == nil { + t.Fatal("expected error on malformed JSON response, got nil") + } + if resp != nil { + t.Errorf("expected nil response on error, got %#v", resp) + } + if !strings.Contains(err.Error(), "invalid character") { + t.Errorf("expected JSON unmarshal error, got: %v", err) + } +} + +func TestUpdatePortCheckV2ReturnsErrorWhenRequestFails(t *testing.T) { + // Use an unreachable address to trigger a connection error + unreachableClient := NewConfigurableClient("apiKey", "realm", ClientArgs{publicBaseUrl: "http://127.0.0.1:1"}) + + err := json.Unmarshal([]byte(updatePortCheckV2Body), &inputPortCheckV2Update) + if err != nil { + t.Fatal(err) + } + + _, _, err = unreachableClient.UpdatePortCheckV2(1650, &inputPortCheckV2Update) + + if err == nil { + t.Fatal("expected connection error, got nil") + } +} diff --git a/syntheticsclientv2/update_sslcheckv2_test.go b/syntheticsclientv2/update_sslcheckv2_test.go index 8e4459d..2f53458 100644 --- a/syntheticsclientv2/update_sslcheckv2_test.go +++ b/syntheticsclientv2/update_sslcheckv2_test.go @@ -22,6 +22,7 @@ import ( "io" "net/http" "reflect" + "strings" "testing" ) @@ -332,3 +333,49 @@ func TestUpdateSslCheckV2BlankResponse(t *testing.T) { t.Fatal("expected non-nil response for blank successful update body") } } + +func TestUpdateSslCheckV2ReturnsErrorOnMalformedResponse(t *testing.T) { + setup() + defer teardown() + + testMux.HandleFunc("/tests/ssl/1650", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + _, err := w.Write([]byte("{not valid json")) + if err != nil { + t.Fatal(err) + } + }) + + err := json.Unmarshal([]byte(updateSslCheckV2Body), &inputSslCheckV2Update) + if err != nil { + t.Fatal(err) + } + + resp, _, err := testClient.UpdateSslCheckV2(1650, &inputSslCheckV2Update) + + if err == nil { + t.Fatal("expected error on malformed JSON response, got nil") + } + if resp != nil { + t.Errorf("expected nil response on error, got %#v", resp) + } + if !strings.Contains(err.Error(), "invalid character") { + t.Errorf("expected JSON unmarshal error, got: %v", err) + } +} + +func TestUpdateSslCheckV2ReturnsErrorWhenRequestFails(t *testing.T) { + // Use an unreachable address to trigger a connection error + unreachableClient := NewConfigurableClient("apiKey", "realm", ClientArgs{publicBaseUrl: "http://127.0.0.1:1"}) + + err := json.Unmarshal([]byte(updateSslCheckV2Body), &inputSslCheckV2Update) + if err != nil { + t.Fatal(err) + } + + _, _, err = unreachableClient.UpdateSslCheckV2(1650, &inputSslCheckV2Update) + + if err == nil { + t.Fatal("expected connection error, got nil") + } +} diff --git a/syntheticsclientv2/update_variablesv2_test.go b/syntheticsclientv2/update_variablesv2_test.go index ee69d14..abdc237 100644 --- a/syntheticsclientv2/update_variablesv2_test.go +++ b/syntheticsclientv2/update_variablesv2_test.go @@ -71,3 +71,74 @@ func TestUpdateVariableV2(t *testing.T) { } } + +func TestUpdateVariableV2ReturnsErrorOnMalformedResponse(t *testing.T) { + setup() + defer teardown() + + err := json.Unmarshal([]byte(updateVariableV2Body), &inputVariableV2Update) + if err != nil { + t.Fatal(err) + } + + testMux.HandleFunc("/variables/10", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + _, err := w.Write([]byte("{malformed response}")) + if err != nil { + t.Fatal(err) + } + }) + + resp, _, err := testClient.UpdateVariableV2(10, &inputVariableV2Update) + if err == nil { + t.Fatal("expected error on malformed response, got nil") + } + if resp != nil { + t.Errorf("expected nil response on parse error, got %#v", resp) + } +} + +func TestUpdateVariableV2ReturnsErrorOnNetworkFailure(t *testing.T) { + unreachableClient := NewConfigurableClient("apiKey", "realm", ClientArgs{publicBaseUrl: "http://127.0.0.1:1"}) + + inputData := VariableV2Input{ + Variable: Variable{ + Name: "test-var", + Value: "test-value", + Secret: false, + Description: "test description", + }, + } + + _, _, err := unreachableClient.UpdateVariableV2(10, &inputData) + if err == nil { + t.Fatal("expected connection error, got nil") + } +} + +func TestUpdateVariableV2ReturnsNonNilResponseOnEmptyBody(t *testing.T) { + setup() + defer teardown() + + err := json.Unmarshal([]byte(updateVariableV2Body), &inputVariableV2Update) + if err != nil { + t.Fatal(err) + } + + testMux.HandleFunc("/variables/20", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + w.WriteHeader(http.StatusOK) + _, err := w.Write([]byte("")) + if err != nil { + t.Fatal(err) + } + }) + + resp, _, err := testClient.UpdateVariableV2(20, &inputVariableV2Update) + if err != nil { + t.Fatal(err) + } + if resp == nil { + t.Fatal("expected non-nil response for empty body with 2xx status") + } +} From 18bd75f54905988820ba43450f03553c6679bb22 Mon Sep 17 00:00:00 2001 From: Jaideep Padhye Date: Fri, 7 Aug 2026 21:23:54 -0700 Subject: [PATCH 06/13] SYN-6889: drop the unit_tests build tag The repo only has unit tests (integration_test.go already carries its own integration tag), so gating every test file behind -tags=unit_tests served no purpose beyond requiring the flag on every go test/build invocation. Co-Authored-By: Claude --- Makefile | 6 +++--- syntheticsclientv2/clientcertificate_models_test.go | 3 --- syntheticsclientv2/common_models_test.go | 3 --- syntheticsclientv2/create_apicheckv2_test.go | 3 --- syntheticsclientv2/create_browsercheckv2_test.go | 3 --- syntheticsclientv2/create_cacertificatev2_test.go | 3 --- syntheticsclientv2/create_clientcertificatev2_test.go | 3 --- syntheticsclientv2/create_downtimeconfigurationv2_test.go | 3 --- syntheticsclientv2/create_httpcheckv2_test.go | 3 --- syntheticsclientv2/create_locationv2_test.go | 3 --- syntheticsclientv2/create_portcheckv2_test.go | 3 --- syntheticsclientv2/create_sslcheckv2_test.go | 3 --- syntheticsclientv2/create_variablev2_test.go | 3 --- syntheticsclientv2/delete_apicheckv2_test.go | 3 --- syntheticsclientv2/delete_browsercheckv2_test.go | 3 --- syntheticsclientv2/delete_cacertificatev2_test.go | 3 --- syntheticsclientv2/delete_clientcertificatev2_test.go | 3 --- syntheticsclientv2/delete_downtimeconfigurationv2_test.go | 3 --- syntheticsclientv2/delete_httpcheckv2_test.go | 3 --- syntheticsclientv2/delete_locationv2_test.go | 3 --- syntheticsclientv2/delete_portcheckv2_test.go | 3 --- syntheticsclientv2/delete_sslcheckv2_test.go | 3 --- syntheticsclientv2/delete_variablesv2_test.go | 3 --- syntheticsclientv2/get_apicheckv2_test.go | 3 --- syntheticsclientv2/get_browsercheckv2_test.go | 3 --- syntheticsclientv2/get_cacertificatesv2_test.go | 3 --- syntheticsclientv2/get_cacertificatev2_test.go | 3 --- syntheticsclientv2/get_checksv2_test.go | 3 --- syntheticsclientv2/get_clientcertificatesv2_test.go | 3 --- syntheticsclientv2/get_clientcertificatev2_test.go | 3 --- syntheticsclientv2/get_devicesv2_test.go | 3 --- syntheticsclientv2/get_downtimeconfigurationsv2_test.go | 3 --- syntheticsclientv2/get_excludedfiletypesv2_test.go | 3 --- syntheticsclientv2/get_httpcheckv2_test.go | 3 --- syntheticsclientv2/get_locationsv2_test.go | 3 --- syntheticsclientv2/get_portcheckv2_test.go | 3 --- syntheticsclientv2/get_sslcheckv2_test.go | 3 --- syntheticsclientv2/get_variablev2_test.go | 3 --- syntheticsclientv2/httpcheckv2_nullable_port_test.go | 3 --- syntheticsclientv2/synthetics_test.go | 3 --- syntheticsclientv2/totpvariablev2_test.go | 3 --- syntheticsclientv2/update_apicheckv2_test.go | 3 --- syntheticsclientv2/update_browsercheckv2_test.go | 3 --- syntheticsclientv2/update_cacertificatev2_test.go | 3 --- syntheticsclientv2/update_clientcertificatev2_test.go | 3 --- syntheticsclientv2/update_downtimeconfigurationv2_test.go | 3 --- syntheticsclientv2/update_httpcheckv2_test.go | 3 --- syntheticsclientv2/update_portcheckv2_test.go | 3 --- syntheticsclientv2/update_sslcheckv2_test.go | 3 --- syntheticsclientv2/update_variablesv2_test.go | 3 --- 50 files changed, 3 insertions(+), 150 deletions(-) diff --git a/Makefile b/Makefile index 220b40e..9be5bd7 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,7 @@ default: test all: clean build test build: fmtcheck - go build -tags=unit_tests $(FILES) + go build $(FILES) clean: @echo "==> Cleaning out old builds " @@ -29,8 +29,8 @@ fmtcheck: fmt lint test: fmtcheck @echo "==> Running all tests" - go test $(FILES) -v -tags=unit_tests -timeout=30s -parallel=4 -cover + go test $(FILES) -v -timeout=30s -parallel=4 -cover test-cover: clean fmtcheck @echo "==> Running all tests with coverage and JSON output" - go test $(FILES) -tags=unit_tests -timeout=30s -parallel=8 -json -cover -covermode=atomic -coverprofile coverage.txt > test-results.json + go test $(FILES) -timeout=30s -parallel=8 -json -cover -covermode=atomic -coverprofile coverage.txt > test-results.json diff --git a/syntheticsclientv2/clientcertificate_models_test.go b/syntheticsclientv2/clientcertificate_models_test.go index 838df4a..6c904bb 100644 --- a/syntheticsclientv2/clientcertificate_models_test.go +++ b/syntheticsclientv2/clientcertificate_models_test.go @@ -1,6 +1,3 @@ -//go:build unit_tests -// +build unit_tests - // Copyright 2026 Splunk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/syntheticsclientv2/common_models_test.go b/syntheticsclientv2/common_models_test.go index b50cedd..c2c1611 100644 --- a/syntheticsclientv2/common_models_test.go +++ b/syntheticsclientv2/common_models_test.go @@ -1,6 +1,3 @@ -//go:build unit_tests -// +build unit_tests - // Copyright 2026 Splunk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/syntheticsclientv2/create_apicheckv2_test.go b/syntheticsclientv2/create_apicheckv2_test.go index f0e17ba..52913c2 100644 --- a/syntheticsclientv2/create_apicheckv2_test.go +++ b/syntheticsclientv2/create_apicheckv2_test.go @@ -1,6 +1,3 @@ -//go:build unit_tests -// +build unit_tests - // Copyright 2021 Splunk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/syntheticsclientv2/create_browsercheckv2_test.go b/syntheticsclientv2/create_browsercheckv2_test.go index e78f1dd..e913f85 100644 --- a/syntheticsclientv2/create_browsercheckv2_test.go +++ b/syntheticsclientv2/create_browsercheckv2_test.go @@ -1,6 +1,3 @@ -//go:build unit_tests -// +build unit_tests - // Copyright 2021 Splunk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/syntheticsclientv2/create_cacertificatev2_test.go b/syntheticsclientv2/create_cacertificatev2_test.go index 8b254b0..5537d44 100644 --- a/syntheticsclientv2/create_cacertificatev2_test.go +++ b/syntheticsclientv2/create_cacertificatev2_test.go @@ -1,6 +1,3 @@ -//go:build unit_tests -// +build unit_tests - // Copyright 2026 Splunk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/syntheticsclientv2/create_clientcertificatev2_test.go b/syntheticsclientv2/create_clientcertificatev2_test.go index 0876986..23a72d0 100644 --- a/syntheticsclientv2/create_clientcertificatev2_test.go +++ b/syntheticsclientv2/create_clientcertificatev2_test.go @@ -1,6 +1,3 @@ -//go:build unit_tests -// +build unit_tests - // Copyright 2026 Splunk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/syntheticsclientv2/create_downtimeconfigurationv2_test.go b/syntheticsclientv2/create_downtimeconfigurationv2_test.go index bd6940a..4981a7b 100644 --- a/syntheticsclientv2/create_downtimeconfigurationv2_test.go +++ b/syntheticsclientv2/create_downtimeconfigurationv2_test.go @@ -1,6 +1,3 @@ -//go:build unit_tests -// +build unit_tests - // Copyright 2024 Splunk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/syntheticsclientv2/create_httpcheckv2_test.go b/syntheticsclientv2/create_httpcheckv2_test.go index 36d5fee..e0b7274 100644 --- a/syntheticsclientv2/create_httpcheckv2_test.go +++ b/syntheticsclientv2/create_httpcheckv2_test.go @@ -1,6 +1,3 @@ -//go:build unit_tests -// +build unit_tests - // Copyright 2021 Splunk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/syntheticsclientv2/create_locationv2_test.go b/syntheticsclientv2/create_locationv2_test.go index e64df6a..7e3a450 100644 --- a/syntheticsclientv2/create_locationv2_test.go +++ b/syntheticsclientv2/create_locationv2_test.go @@ -1,6 +1,3 @@ -//go:build unit_tests -// +build unit_tests - // Copyright 2021 Splunk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/syntheticsclientv2/create_portcheckv2_test.go b/syntheticsclientv2/create_portcheckv2_test.go index f1eeaef..b79354b 100644 --- a/syntheticsclientv2/create_portcheckv2_test.go +++ b/syntheticsclientv2/create_portcheckv2_test.go @@ -1,6 +1,3 @@ -//go:build unit_tests -// +build unit_tests - // Copyright 2021 Splunk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/syntheticsclientv2/create_sslcheckv2_test.go b/syntheticsclientv2/create_sslcheckv2_test.go index 44eab1c..241151a 100644 --- a/syntheticsclientv2/create_sslcheckv2_test.go +++ b/syntheticsclientv2/create_sslcheckv2_test.go @@ -1,6 +1,3 @@ -//go:build unit_tests -// +build unit_tests - // Copyright 2026 Splunk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/syntheticsclientv2/create_variablev2_test.go b/syntheticsclientv2/create_variablev2_test.go index e39d984..b62c45c 100644 --- a/syntheticsclientv2/create_variablev2_test.go +++ b/syntheticsclientv2/create_variablev2_test.go @@ -1,6 +1,3 @@ -//go:build unit_tests -// +build unit_tests - // Copyright 2021 Splunk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/syntheticsclientv2/delete_apicheckv2_test.go b/syntheticsclientv2/delete_apicheckv2_test.go index ee16055..0613d7e 100644 --- a/syntheticsclientv2/delete_apicheckv2_test.go +++ b/syntheticsclientv2/delete_apicheckv2_test.go @@ -1,6 +1,3 @@ -//go:build unit_tests -// +build unit_tests - // Copyright 2021 Splunk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/syntheticsclientv2/delete_browsercheckv2_test.go b/syntheticsclientv2/delete_browsercheckv2_test.go index 2a9718b..e706533 100644 --- a/syntheticsclientv2/delete_browsercheckv2_test.go +++ b/syntheticsclientv2/delete_browsercheckv2_test.go @@ -1,6 +1,3 @@ -//go:build unit_tests -// +build unit_tests - // Copyright 2021 Splunk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/syntheticsclientv2/delete_cacertificatev2_test.go b/syntheticsclientv2/delete_cacertificatev2_test.go index fc25ed9..3e45943 100644 --- a/syntheticsclientv2/delete_cacertificatev2_test.go +++ b/syntheticsclientv2/delete_cacertificatev2_test.go @@ -1,6 +1,3 @@ -//go:build unit_tests -// +build unit_tests - // Copyright 2026 Splunk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/syntheticsclientv2/delete_clientcertificatev2_test.go b/syntheticsclientv2/delete_clientcertificatev2_test.go index 1086b26..661d4d9 100644 --- a/syntheticsclientv2/delete_clientcertificatev2_test.go +++ b/syntheticsclientv2/delete_clientcertificatev2_test.go @@ -1,6 +1,3 @@ -//go:build unit_tests -// +build unit_tests - // Copyright 2026 Splunk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/syntheticsclientv2/delete_downtimeconfigurationv2_test.go b/syntheticsclientv2/delete_downtimeconfigurationv2_test.go index f1c66a0..89e4adb 100644 --- a/syntheticsclientv2/delete_downtimeconfigurationv2_test.go +++ b/syntheticsclientv2/delete_downtimeconfigurationv2_test.go @@ -1,6 +1,3 @@ -//go:build unit_tests -// +build unit_tests - // Copyright 2024 Splunk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/syntheticsclientv2/delete_httpcheckv2_test.go b/syntheticsclientv2/delete_httpcheckv2_test.go index f104a7d..4f1b454 100644 --- a/syntheticsclientv2/delete_httpcheckv2_test.go +++ b/syntheticsclientv2/delete_httpcheckv2_test.go @@ -1,6 +1,3 @@ -//go:build unit_tests -// +build unit_tests - // Copyright 2021 Splunk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/syntheticsclientv2/delete_locationv2_test.go b/syntheticsclientv2/delete_locationv2_test.go index bcb05ee..47bb5ef 100644 --- a/syntheticsclientv2/delete_locationv2_test.go +++ b/syntheticsclientv2/delete_locationv2_test.go @@ -1,6 +1,3 @@ -//go:build unit_tests -// +build unit_tests - // Copyright 2021 Splunk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/syntheticsclientv2/delete_portcheckv2_test.go b/syntheticsclientv2/delete_portcheckv2_test.go index 1789355..ba2eba9 100644 --- a/syntheticsclientv2/delete_portcheckv2_test.go +++ b/syntheticsclientv2/delete_portcheckv2_test.go @@ -1,6 +1,3 @@ -//go:build unit_tests -// +build unit_tests - // Copyright 2021 Splunk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/syntheticsclientv2/delete_sslcheckv2_test.go b/syntheticsclientv2/delete_sslcheckv2_test.go index b9b83d9..fc08730 100644 --- a/syntheticsclientv2/delete_sslcheckv2_test.go +++ b/syntheticsclientv2/delete_sslcheckv2_test.go @@ -1,6 +1,3 @@ -//go:build unit_tests -// +build unit_tests - // Copyright 2026 Splunk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/syntheticsclientv2/delete_variablesv2_test.go b/syntheticsclientv2/delete_variablesv2_test.go index 9011a5b..09adad0 100644 --- a/syntheticsclientv2/delete_variablesv2_test.go +++ b/syntheticsclientv2/delete_variablesv2_test.go @@ -1,6 +1,3 @@ -//go:build unit_tests -// +build unit_tests - // Copyright 2021 Splunk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/syntheticsclientv2/get_apicheckv2_test.go b/syntheticsclientv2/get_apicheckv2_test.go index 6e81957..e38d9a9 100644 --- a/syntheticsclientv2/get_apicheckv2_test.go +++ b/syntheticsclientv2/get_apicheckv2_test.go @@ -1,6 +1,3 @@ -//go:build unit_tests -// +build unit_tests - // Copyright 2021 Splunk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/syntheticsclientv2/get_browsercheckv2_test.go b/syntheticsclientv2/get_browsercheckv2_test.go index 32d99ee..88915f5 100644 --- a/syntheticsclientv2/get_browsercheckv2_test.go +++ b/syntheticsclientv2/get_browsercheckv2_test.go @@ -1,6 +1,3 @@ -//go:build unit_tests -// +build unit_tests - // Copyright 2021 Splunk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/syntheticsclientv2/get_cacertificatesv2_test.go b/syntheticsclientv2/get_cacertificatesv2_test.go index b8d8602..4fdfd05 100644 --- a/syntheticsclientv2/get_cacertificatesv2_test.go +++ b/syntheticsclientv2/get_cacertificatesv2_test.go @@ -1,6 +1,3 @@ -//go:build unit_tests -// +build unit_tests - // Copyright 2026 Splunk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/syntheticsclientv2/get_cacertificatev2_test.go b/syntheticsclientv2/get_cacertificatev2_test.go index 1239163..a59ac1c 100644 --- a/syntheticsclientv2/get_cacertificatev2_test.go +++ b/syntheticsclientv2/get_cacertificatev2_test.go @@ -1,6 +1,3 @@ -//go:build unit_tests -// +build unit_tests - // Copyright 2026 Splunk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/syntheticsclientv2/get_checksv2_test.go b/syntheticsclientv2/get_checksv2_test.go index 5e45298..bc17bde 100644 --- a/syntheticsclientv2/get_checksv2_test.go +++ b/syntheticsclientv2/get_checksv2_test.go @@ -1,6 +1,3 @@ -//go:build unit_tests -// +build unit_tests - // Copyright 2021 Splunk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/syntheticsclientv2/get_clientcertificatesv2_test.go b/syntheticsclientv2/get_clientcertificatesv2_test.go index fed4b86..fb4783b 100644 --- a/syntheticsclientv2/get_clientcertificatesv2_test.go +++ b/syntheticsclientv2/get_clientcertificatesv2_test.go @@ -1,6 +1,3 @@ -//go:build unit_tests -// +build unit_tests - // Copyright 2026 Splunk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/syntheticsclientv2/get_clientcertificatev2_test.go b/syntheticsclientv2/get_clientcertificatev2_test.go index 5dda83e..0a2b705 100644 --- a/syntheticsclientv2/get_clientcertificatev2_test.go +++ b/syntheticsclientv2/get_clientcertificatev2_test.go @@ -1,6 +1,3 @@ -//go:build unit_tests -// +build unit_tests - // Copyright 2026 Splunk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/syntheticsclientv2/get_devicesv2_test.go b/syntheticsclientv2/get_devicesv2_test.go index 41ac39e..e280783 100644 --- a/syntheticsclientv2/get_devicesv2_test.go +++ b/syntheticsclientv2/get_devicesv2_test.go @@ -1,6 +1,3 @@ -//go:build unit_tests -// +build unit_tests - // Copyright 2021 Splunk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/syntheticsclientv2/get_downtimeconfigurationsv2_test.go b/syntheticsclientv2/get_downtimeconfigurationsv2_test.go index 87d78b8..4db29c5 100644 --- a/syntheticsclientv2/get_downtimeconfigurationsv2_test.go +++ b/syntheticsclientv2/get_downtimeconfigurationsv2_test.go @@ -1,6 +1,3 @@ -//go:build unit_tests -// +build unit_tests - // Copyright 2024 Splunk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/syntheticsclientv2/get_excludedfiletypesv2_test.go b/syntheticsclientv2/get_excludedfiletypesv2_test.go index 59c1da3..c9235a7 100644 --- a/syntheticsclientv2/get_excludedfiletypesv2_test.go +++ b/syntheticsclientv2/get_excludedfiletypesv2_test.go @@ -1,6 +1,3 @@ -//go:build unit_tests -// +build unit_tests - // Copyright 2021 Splunk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/syntheticsclientv2/get_httpcheckv2_test.go b/syntheticsclientv2/get_httpcheckv2_test.go index 071005a..94cf24c 100644 --- a/syntheticsclientv2/get_httpcheckv2_test.go +++ b/syntheticsclientv2/get_httpcheckv2_test.go @@ -1,6 +1,3 @@ -//go:build unit_tests -// +build unit_tests - // Copyright 2021 Splunk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/syntheticsclientv2/get_locationsv2_test.go b/syntheticsclientv2/get_locationsv2_test.go index bd57196..1d562a5 100644 --- a/syntheticsclientv2/get_locationsv2_test.go +++ b/syntheticsclientv2/get_locationsv2_test.go @@ -1,6 +1,3 @@ -//go:build unit_tests -// +build unit_tests - // Copyright 2021 Splunk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/syntheticsclientv2/get_portcheckv2_test.go b/syntheticsclientv2/get_portcheckv2_test.go index b227ad2..a925856 100644 --- a/syntheticsclientv2/get_portcheckv2_test.go +++ b/syntheticsclientv2/get_portcheckv2_test.go @@ -1,6 +1,3 @@ -//go:build unit_tests -// +build unit_tests - // Copyright 2021 Splunk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/syntheticsclientv2/get_sslcheckv2_test.go b/syntheticsclientv2/get_sslcheckv2_test.go index 0a86dde..bd6d4ea 100644 --- a/syntheticsclientv2/get_sslcheckv2_test.go +++ b/syntheticsclientv2/get_sslcheckv2_test.go @@ -1,6 +1,3 @@ -//go:build unit_tests -// +build unit_tests - // Copyright 2026 Splunk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/syntheticsclientv2/get_variablev2_test.go b/syntheticsclientv2/get_variablev2_test.go index afe7440..94fc536 100644 --- a/syntheticsclientv2/get_variablev2_test.go +++ b/syntheticsclientv2/get_variablev2_test.go @@ -1,6 +1,3 @@ -//go:build unit_tests -// +build unit_tests - // Copyright 2021 Splunk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/syntheticsclientv2/httpcheckv2_nullable_port_test.go b/syntheticsclientv2/httpcheckv2_nullable_port_test.go index 2e98604..43fe1bf 100644 --- a/syntheticsclientv2/httpcheckv2_nullable_port_test.go +++ b/syntheticsclientv2/httpcheckv2_nullable_port_test.go @@ -1,6 +1,3 @@ -//go:build unit_tests -// +build unit_tests - // Copyright 2021 Splunk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/syntheticsclientv2/synthetics_test.go b/syntheticsclientv2/synthetics_test.go index f1834c0..ed57724 100644 --- a/syntheticsclientv2/synthetics_test.go +++ b/syntheticsclientv2/synthetics_test.go @@ -1,6 +1,3 @@ -//go:build unit_tests -// +build unit_tests - // Copyright 2021 Splunk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/syntheticsclientv2/totpvariablev2_test.go b/syntheticsclientv2/totpvariablev2_test.go index 2aba71f..05771b7 100644 --- a/syntheticsclientv2/totpvariablev2_test.go +++ b/syntheticsclientv2/totpvariablev2_test.go @@ -1,6 +1,3 @@ -//go:build unit_tests -// +build unit_tests - // Copyright 2026 Splunk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/syntheticsclientv2/update_apicheckv2_test.go b/syntheticsclientv2/update_apicheckv2_test.go index 5f4fb10..c6a9d43 100644 --- a/syntheticsclientv2/update_apicheckv2_test.go +++ b/syntheticsclientv2/update_apicheckv2_test.go @@ -1,6 +1,3 @@ -//go:build unit_tests -// +build unit_tests - // Copyright 2021 Splunk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/syntheticsclientv2/update_browsercheckv2_test.go b/syntheticsclientv2/update_browsercheckv2_test.go index b88a380..c8ed0c4 100644 --- a/syntheticsclientv2/update_browsercheckv2_test.go +++ b/syntheticsclientv2/update_browsercheckv2_test.go @@ -1,6 +1,3 @@ -//go:build unit_tests -// +build unit_tests - // Copyright 2021 Splunk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/syntheticsclientv2/update_cacertificatev2_test.go b/syntheticsclientv2/update_cacertificatev2_test.go index 61edc50..e4563c1 100644 --- a/syntheticsclientv2/update_cacertificatev2_test.go +++ b/syntheticsclientv2/update_cacertificatev2_test.go @@ -1,6 +1,3 @@ -//go:build unit_tests -// +build unit_tests - // Copyright 2026 Splunk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/syntheticsclientv2/update_clientcertificatev2_test.go b/syntheticsclientv2/update_clientcertificatev2_test.go index efd4a96..7980542 100644 --- a/syntheticsclientv2/update_clientcertificatev2_test.go +++ b/syntheticsclientv2/update_clientcertificatev2_test.go @@ -1,6 +1,3 @@ -//go:build unit_tests -// +build unit_tests - // Copyright 2026 Splunk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/syntheticsclientv2/update_downtimeconfigurationv2_test.go b/syntheticsclientv2/update_downtimeconfigurationv2_test.go index 7ff6fad..dc2667d 100644 --- a/syntheticsclientv2/update_downtimeconfigurationv2_test.go +++ b/syntheticsclientv2/update_downtimeconfigurationv2_test.go @@ -1,6 +1,3 @@ -//go:build unit_tests -// +build unit_tests - // Copyright 2024 Splunk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/syntheticsclientv2/update_httpcheckv2_test.go b/syntheticsclientv2/update_httpcheckv2_test.go index 730ffdb..ecbd5ff 100644 --- a/syntheticsclientv2/update_httpcheckv2_test.go +++ b/syntheticsclientv2/update_httpcheckv2_test.go @@ -1,6 +1,3 @@ -//go:build unit_tests -// +build unit_tests - // Copyright 2021 Splunk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/syntheticsclientv2/update_portcheckv2_test.go b/syntheticsclientv2/update_portcheckv2_test.go index 620a602..83791bb 100644 --- a/syntheticsclientv2/update_portcheckv2_test.go +++ b/syntheticsclientv2/update_portcheckv2_test.go @@ -1,6 +1,3 @@ -//go:build unit_tests -// +build unit_tests - // Copyright 2021 Splunk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/syntheticsclientv2/update_sslcheckv2_test.go b/syntheticsclientv2/update_sslcheckv2_test.go index 2f53458..dafc2b8 100644 --- a/syntheticsclientv2/update_sslcheckv2_test.go +++ b/syntheticsclientv2/update_sslcheckv2_test.go @@ -1,6 +1,3 @@ -//go:build unit_tests -// +build unit_tests - // Copyright 2026 Splunk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/syntheticsclientv2/update_variablesv2_test.go b/syntheticsclientv2/update_variablesv2_test.go index abdc237..ce73e41 100644 --- a/syntheticsclientv2/update_variablesv2_test.go +++ b/syntheticsclientv2/update_variablesv2_test.go @@ -1,6 +1,3 @@ -//go:build unit_tests -// +build unit_tests - // Copyright 2021 Splunk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); From 3ec9257f72eb81d4e9075df374bbc4e8cbdfee5e Mon Sep 17 00:00:00 2001 From: Jaideep Padhye Date: Fri, 7 Aug 2026 21:30:34 -0700 Subject: [PATCH 07/13] SYN-6889: check w.Write errors flagged by lint after unit_tests tag removal Removing the unit_tests build tag from Makefile lint invocations exposed these test files to golangci-lint's errcheck for the first time, which flagged unchecked httptest handler w.Write return values. --- .../create_downtimeconfigurationv2_test.go | 5 +++- syntheticsclientv2/create_locationv2_test.go | 5 +++- syntheticsclientv2/get_devicesv2_test.go | 5 +++- .../get_downtimeconfigurationsv2_test.go | 5 +++- .../get_excludedfiletypesv2_test.go | 5 +++- syntheticsclientv2/get_locationsv2_test.go | 10 +++++-- syntheticsclientv2/synthetics_test.go | 30 +++++++++++++++---- .../update_downtimeconfigurationv2_test.go | 5 +++- 8 files changed, 56 insertions(+), 14 deletions(-) diff --git a/syntheticsclientv2/create_downtimeconfigurationv2_test.go b/syntheticsclientv2/create_downtimeconfigurationv2_test.go index 4981a7b..1691ea4 100644 --- a/syntheticsclientv2/create_downtimeconfigurationv2_test.go +++ b/syntheticsclientv2/create_downtimeconfigurationv2_test.go @@ -92,7 +92,10 @@ func TestCreateDowntimeConfigurationV2ReturnsErrorOnMalformedResponse(t *testing testMux.HandleFunc("/downtime_configurations", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, "POST") - w.Write([]byte("{not valid json")) + _, err := w.Write([]byte("{not valid json")) + if err != nil { + t.Fatal(err) + } }) resp, details, err := testClient.CreateDowntimeConfigurationV2(&DowntimeConfigurationV2Input{}) diff --git a/syntheticsclientv2/create_locationv2_test.go b/syntheticsclientv2/create_locationv2_test.go index 7e3a450..b1c109d 100644 --- a/syntheticsclientv2/create_locationv2_test.go +++ b/syntheticsclientv2/create_locationv2_test.go @@ -68,7 +68,10 @@ func TestCreateLocationV2ReturnsErrorOnMalformedResponse(t *testing.T) { testMux.HandleFunc("/locations", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, "POST") - w.Write([]byte("{not valid json")) + _, err := w.Write([]byte("{not valid json")) + if err != nil { + t.Fatal(err) + } }) resp, details, err := testClient.CreateLocationV2(&LocationV2Input{}) diff --git a/syntheticsclientv2/get_devicesv2_test.go b/syntheticsclientv2/get_devicesv2_test.go index e280783..f8807f7 100644 --- a/syntheticsclientv2/get_devicesv2_test.go +++ b/syntheticsclientv2/get_devicesv2_test.go @@ -85,7 +85,10 @@ func TestGetDevicesV2ReturnsErrorOnMalformedResponse(t *testing.T) { testMux.HandleFunc("/devices", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, "GET") - w.Write([]byte("{not valid json")) + _, err := w.Write([]byte("{not valid json")) + if err != nil { + t.Fatal(err) + } }) resp, details, err := testClient.GetDevicesV2() diff --git a/syntheticsclientv2/get_downtimeconfigurationsv2_test.go b/syntheticsclientv2/get_downtimeconfigurationsv2_test.go index 4db29c5..abb6b47 100644 --- a/syntheticsclientv2/get_downtimeconfigurationsv2_test.go +++ b/syntheticsclientv2/get_downtimeconfigurationsv2_test.go @@ -149,7 +149,10 @@ func TestGetDowntimeConfigurationsV2ReturnsErrorOnMalformedResponse(t *testing.T testMux.HandleFunc("/downtime_configurations", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, "GET") - w.Write([]byte("{not valid json")) + _, err := w.Write([]byte("{not valid json")) + if err != nil { + t.Fatal(err) + } }) resp, details, err := testClient.GetDowntimeConfigurationsV2(&GetDowntimeConfigurationsV2Options{}) diff --git a/syntheticsclientv2/get_excludedfiletypesv2_test.go b/syntheticsclientv2/get_excludedfiletypesv2_test.go index c9235a7..ec0fdcc 100644 --- a/syntheticsclientv2/get_excludedfiletypesv2_test.go +++ b/syntheticsclientv2/get_excludedfiletypesv2_test.go @@ -69,7 +69,10 @@ func TestGetExcludedFileTypesV2ReturnsErrorOnMalformedResponse(t *testing.T) { testMux.HandleFunc("/excluded_file_types", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, "GET") - w.Write([]byte("{not valid json")) + _, err := w.Write([]byte("{not valid json")) + if err != nil { + t.Fatal(err) + } }) resp, details, err := testClient.GetExcludedFileTypesV2() diff --git a/syntheticsclientv2/get_locationsv2_test.go b/syntheticsclientv2/get_locationsv2_test.go index 1d562a5..ac4c16b 100644 --- a/syntheticsclientv2/get_locationsv2_test.go +++ b/syntheticsclientv2/get_locationsv2_test.go @@ -144,7 +144,10 @@ func TestGetLocationsV2ReturnsErrorOnMalformedResponse(t *testing.T) { testMux.HandleFunc("/locations/", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, "GET") - w.Write([]byte("{not valid json")) + _, err := w.Write([]byte("{not valid json")) + if err != nil { + t.Fatal(err) + } }) resp, details, err := testClient.GetLocationsV2() @@ -173,7 +176,10 @@ func TestGetLocationV2ReturnsErrorOnMalformedResponse(t *testing.T) { testMux.HandleFunc("/locations/aws-us-east-1", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, "GET") - w.Write([]byte("{not valid json")) + _, err := w.Write([]byte("{not valid json")) + if err != nil { + t.Fatal(err) + } }) resp, details, err := testClient.GetLocationV2("aws-us-east-1") diff --git a/syntheticsclientv2/synthetics_test.go b/syntheticsclientv2/synthetics_test.go index ed57724..26c0501 100644 --- a/syntheticsclientv2/synthetics_test.go +++ b/syntheticsclientv2/synthetics_test.go @@ -104,7 +104,10 @@ func TestConfigurableClientErrorStatusCode(t *testing.T) { testMux.HandleFunc("/tests", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNotFound) - w.Write([]byte(`{"status":"404"}`)) + _, err := w.Write([]byte(`{"status":"404"}`)) + if err != nil { + t.Fatal(err) + } }) testConfigurableClient := NewConfigurableClient("apiKey", "realm", ClientArgs{ @@ -128,7 +131,10 @@ func TestMakePublicAPICallRedactsAPIKeyFromRequestDetails(t *testing.T) { testMux.HandleFunc("/tests", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) - w.Write([]byte(`{}`)) + _, err := w.Write([]byte(`{}`)) + if err != nil { + t.Fatal(err) + } }) apiKey := "secret-api-key" @@ -157,7 +163,10 @@ func TestMakePublicAPICallRedactsCaCertificateContentFromRequestDetails(t *testi testMux.HandleFunc("/cacerts", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) - w.Write([]byte(`{}`)) + _, err := w.Write([]byte(`{}`)) + if err != nil { + t.Fatal(err) + } }) apiKey := "secret-api-key" @@ -232,7 +241,10 @@ func TestCreateCaCertificateV2RedactsRequestDetails(t *testing.T) { testMux.HandleFunc("/cacerts", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, "POST") w.WriteHeader(http.StatusOK) - w.Write([]byte(`{"cacert":{"id":1,"name":"test-ca","description":"private test CA","content":"","fileExtension":"pem","filename":"ca.pem"}}`)) + _, err := w.Write([]byte(`{"cacert":{"id":1,"name":"test-ca","description":"private test CA","content":"","fileExtension":"pem","filename":"ca.pem"}}`)) + if err != nil { + t.Fatal(err) + } }) apiKey := "secret-api-key" @@ -455,7 +467,10 @@ func TestMakePublicAPICallSetsQueryParams(t *testing.T) { t.Errorf("returned query param \n\n%#v want \n\n%#v", got, "2") } w.WriteHeader(http.StatusOK) - w.Write([]byte(`{}`)) + _, err := w.Write([]byte(`{}`)) + if err != nil { + t.Fatal(err) + } }) testConfigurableClient := NewConfigurableClient("apiKey", "realm", ClientArgs{ @@ -554,7 +569,10 @@ func TestMakePublicAPICallDoesNotExposeRawRequest(t *testing.T) { testMux.HandleFunc("/certificates", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) - w.Write([]byte(`{}`)) + _, err := w.Write([]byte(`{}`)) + if err != nil { + t.Fatal(err) + } }) apiKey := "secret-api-key" diff --git a/syntheticsclientv2/update_downtimeconfigurationv2_test.go b/syntheticsclientv2/update_downtimeconfigurationv2_test.go index dc2667d..e306567 100644 --- a/syntheticsclientv2/update_downtimeconfigurationv2_test.go +++ b/syntheticsclientv2/update_downtimeconfigurationv2_test.go @@ -82,7 +82,10 @@ func TestUpdateDowntimeConfigurationV2ReturnsErrorOnMalformedResponse(t *testing testMux.HandleFunc("/downtime_configurations/10", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, "PUT") - w.Write([]byte("{not valid json")) + _, err := w.Write([]byte("{not valid json")) + if err != nil { + t.Fatal(err) + } }) resp, details, err := testClient.UpdateDowntimeConfigurationV2(10, &DowntimeConfigurationV2Input{}) From d4fe307cbb0489b8654d1d105017cee1f99d881b Mon Sep 17 00:00:00 2001 From: Jaideep Padhye Date: Mon, 10 Aug 2026 10:37:45 -0700 Subject: [PATCH 08/13] Add security-gated CI for the live integration test suite Ports the privilege-separated pr-metadata + workflow_run pattern from splunk/terraform-provider-synthetics so the live suite's API_ACCESS_TOKEN can run in CI without ever being reachable from an untrusted fork-PR context, and only executes after a maintainer approves via the integration-test GitHub Environment. - .github/workflows/pr-metadata.yml: untrusted, no-secrets workflow that records the PR number/head SHA as an artifact. - .github/workflows/integration-test.yml: trusted workflow_run workflow. `resolve` validates the artifact against forgery (cross-checked against workflow_run's own head_sha and an independent pulls.get lookup) and opens a queued Check Run; `integration-test` is gated on the integration-test environment's required-reviewer approval and runs `make test-integration`; `report` posts the parsed result table and closes out the Check Run, kept as its own job so pull-requests:write/ checks:write never coexist on a runner that just ran fork PR code. - Makefile: add test-integration, scrubbing the X-Sf-Token header line from the go test -json log before it's captured. - integration_test.go: TestMain now reclaims any leftover beep-var / terraform-test-foo-301 fixtures before creating them, so a cancelled run doesn't wedge every subsequent run behind a duplicate-name error. - .gitignore: ignore .env.testacc (local live-test credentials) and integration.jsonl. Co-Authored-By: Claude --- .github/workflows/integration-test.yml | 341 +++++++ .github/workflows/pr-metadata.yml | 36 + .gitignore | 4 + Makefile | 17 +- syntheticsclientv2/integration_test.go | 1266 +++++++++++++++++++++--- 5 files changed, 1514 insertions(+), 150 deletions(-) create mode 100644 .github/workflows/integration-test.yml create mode 100644 .github/workflows/pr-metadata.yml diff --git a/.github/workflows/integration-test.yml b/.github/workflows/integration-test.yml new file mode 100644 index 0000000..ea4175e --- /dev/null +++ b/.github/workflows/integration-test.yml @@ -0,0 +1,341 @@ +name: Integration Test + +# Trusted trigger: runs in the base repo's context (full permissions model below, +# environment secrets available) regardless of whether the triggering PR came from a +# fork. This is the privilege-separated counterpart to pr-metadata.yml, which does the +# untrusted checkout-free bookkeeping. +# See: https://docs.github.com/en/actions/security-for-github-actions/security-guides/security-hardening-for-github-actions +# +# workflow_run does not automatically attach as a PR status check, so this workflow +# explicitly creates/updates a Check Run on the PR's head SHA via the Checks API — that's +# what branch protection actually watches. +# +# run-name names the PR being tested: without it, every run of this workflow shows as +# "Integration Test" on branch v2 in the Actions list and in the environment-approval +# prompt, and a reviewer approving the live-token gate can't tell which PR they're +# approving from that alone. +run-name: >- + Integration Test: ${{ github.event.workflow_run.head_repository.full_name }}@${{ github.event.workflow_run.head_branch }} + (${{ github.event.workflow_run.head_sha }}) + +on: + workflow_run: + workflows: ["PR Metadata"] + types: [completed] + +permissions: {} + +concurrency: + group: integration-test-${{ github.event.workflow_run.head_repository.full_name }}-${{ github.event.workflow_run.head_branch }} + # The live suite provisions fixed-name resources (see integration_test.go TestMain), so + # two concurrent runs against the same org collide. Queue instead of cancelling: a + # cancelled Go test binary skips t.Cleanup and TestMain's post-run deletes, so cancelling + # a run trades a wait for a batch of orphaned live resources. + cancel-in-progress: false + +jobs: + # Ungated on purpose: downloads and validates the pr-metadata artifact immediately, + # before any approval wait. An environment approval can take up to 30 days (GitHub's own + # timeout), which would otherwise force the artifact retention window to also cover the + # entire approval SLA. Resolving here means only this job's (short) runtime needs the + # artifact to still exist, not however long a reviewer takes to click approve. + resolve: + name: resolve + if: github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'pull_request' + runs-on: ubuntu-latest + timeout-minutes: 5 + permissions: + actions: read + checks: write + outputs: + found: ${{ steps.pr.outputs.found }} + number: ${{ steps.pr.outputs.number }} + sha: ${{ steps.pr.outputs.sha }} + + steps: + - name: Download PR metadata + id: download + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 + with: + name: pr-metadata + path: pr-metadata + github-token: ${{ github.token }} + run-id: ${{ github.event.workflow_run.id }} + + - name: Read and validate PR metadata + id: pr + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 + with: + script: | + const fs = require('fs'); + + // pr-metadata.yml is a pull_request-triggered workflow, so its *file* can be + // rewritten by a fork PR to upload arbitrary pr-number/pr-sha content — never + // trust the artifact as-is. Enforce strict formats and cross-check against + // fields GitHub itself attaches to this trusted workflow_run event, which a + // rewritten pr-metadata.yml cannot forge. + // + // The job-level guard above (conclusion == 'success') already means a draft + // PR (whose record-pr job is skipped, reporting conclusion 'skipped') never + // reaches this job at all. So getting here with a missing or malformed + // artifact is always an anomaly, not a legitimate "nothing to test" case: fail + // loudly rather than silently exiting success with no check or comment. + const rawNumber = fs.readFileSync('pr-metadata/pr-number', 'utf8').trim(); + const rawSha = fs.readFileSync('pr-metadata/pr-sha', 'utf8').trim(); + + if (!/^[0-9]+$/.test(rawNumber)) { + core.setFailed(`pr-metadata artifact has malformed pr-number: ${JSON.stringify(rawNumber)}`); + return; + } + if (!/^[0-9a-f]{40}$/.test(rawSha)) { + core.setFailed(`pr-metadata artifact has malformed pr-sha: ${JSON.stringify(rawSha)}`); + return; + } + + // github.event.workflow_run.head_sha is the commit that triggered the + // pr-metadata.yml run itself — a value GitHub computes and attaches to the + // workflow_run event independently of anything pr-metadata.yml's script did. + // Trust this, not the artifact's own pr-sha file. + const trustedSha = context.payload.workflow_run.head_sha; + if (rawSha !== trustedSha) { + core.setFailed(`pr-metadata artifact sha (${rawSha}) does not match the workflow_run's own head_sha (${trustedSha}) — refusing to trust a forged or stale artifact.`); + return; + } + + // Fetch the PR the artifact claims by number, then verify every field that + // matters against values GitHub itself attached to this trusted workflow_run + // event — never trust the artifact's number on its own. + // + // Deliberately not using listPullRequestsAssociatedWithCommit or the + // workflow_run payload's own `pull_requests` field: (a) the commit-associated + // lookup can return multiple open/merged PRs for the same commit (e.g. a + // commit shared via cherry-pick or a stacked branch), letting a forged + // artifact redirect handling to an unrelated but-also-matching PR; and (b) + // workflow_run.pull_requests is documented to come back empty for PRs from + // forked repositories, which is precisely the case this workflow exists to + // support. Instead: fetch the claimed PR by number directly, then require + // every trust-relevant field to match values GitHub attached to this event. + let pr; + try { + const resp = await github.rest.pulls.get({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: Number(rawNumber), + }); + pr = resp.data; + } catch (e) { + core.setFailed(`Could not fetch PR #${rawNumber} via the API — refusing to trust the pr-metadata artifact: ${e.message}`); + return; + } + + const headRepo = context.payload.workflow_run.head_repository; + const baseRepo = context.payload.workflow_run.repository; + const headBranch = context.payload.workflow_run.head_branch; + + if (pr.state !== 'open') { + core.setFailed(`PR #${rawNumber} is not open (state: ${pr.state}) — refusing to trust the pr-metadata artifact.`); + return; + } + if (pr.draft) { + core.setFailed(`PR #${rawNumber} is a draft — refusing to run live integration tests against it.`); + return; + } + if (pr.head.sha !== trustedSha) { + core.setFailed(`PR #${rawNumber}'s current head sha (${pr.head.sha}) does not match the trusted workflow_run head_sha (${trustedSha}) — PR head moved or artifact is stale/forged.`); + return; + } + if (!pr.head.repo || pr.head.repo.full_name !== headRepo.full_name) { + core.setFailed(`PR #${rawNumber}'s head repo (${pr.head.repo && pr.head.repo.full_name}) does not match the trusted workflow_run head_repository (${headRepo.full_name}) — refusing to trust the pr-metadata artifact.`); + return; + } + if (pr.head.ref !== headBranch) { + core.setFailed(`PR #${rawNumber}'s head branch (${pr.head.ref}) does not match the trusted workflow_run head_branch (${headBranch}) — refusing to trust the pr-metadata artifact.`); + return; + } + if (pr.base.repo.full_name !== baseRepo.full_name) { + core.setFailed(`PR #${rawNumber}'s base repo (${pr.base.repo.full_name}) is not this repository — refusing to trust the pr-metadata artifact.`); + return; + } + + core.setOutput('found', 'true'); + core.setOutput('number', String(pr.number)); + core.setOutput('sha', trustedSha); + + - name: Create pending check run + if: steps.pr.outputs.found == 'true' + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 + env: + PR_SHA: ${{ steps.pr.outputs.sha }} + with: + script: | + await github.rest.checks.create({ + owner: context.repo.owner, + repo: context.repo.repo, + name: 'integration-test', + head_sha: process.env.PR_SHA, + status: 'queued', + details_url: `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`, + }); + + integration-test: + name: integration-test + needs: resolve + if: needs.resolve.outputs.found == 'true' + runs-on: ubuntu-latest + timeout-minutes: 25 + environment: integration-test # gate: waits for reviewer approval here + # Deliberately just contents: read — this job runs a fork PR's code with a live API + # token in its environment. Reporting the outcome (checks:write, pull-requests:write) + # is split into the ungated `report` job below so fork code never runs on a runner that + # also holds write access to checks or PR comments. + permissions: + contents: read + + steps: + - name: Checkout PR head + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + # Checkout by the exact commit SHA recorded by `resolve`, not refs/pull//head: + # that ref is mutable and the environment approval above can sit for hours or + # days, during which the PR could gain new, unreviewed commits. + ref: ${{ needs.resolve.outputs.sha }} + persist-credentials: false + + - name: Verify checked-out SHA matches recorded PR head + env: + PR_SHA: ${{ needs.resolve.outputs.sha }} + run: | + actual="$(git rev-parse HEAD)" + if [ "$actual" != "$PR_SHA" ]; then + echo "Checked-out SHA ($actual) does not match PR metadata ($PR_SHA) — refusing to run privileged tests against an unexpected checkout." >&2 + exit 1 + fi + + - name: Setup Go + uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 + with: + go-version-file: 'go.mod' + # No build cache here: workflow_run executes on the base repo's default-branch + # ref, so a cache written in this job would be saved into the shared v2 cache + # scope and later restored by trusted CI runs — a poisoning path for fork code + # to persist beyond its own PR. Every other job that needs a fast Go setup runs + # on ci.yml, which is unaffected by this. + cache: false + + - name: Run live integration tests + env: + API_ACCESS_TOKEN: ${{ secrets.API_ACCESS_TOKEN }} + REALM: ${{ secrets.REALM }} + run: make test-integration + + - name: Upload integration test log + # Always upload, even on failure, so the report job can render a result table + # instead of just "the job failed". make test-integration already strips the + # X-Sf-Token header line from this log before it's written. + if: always() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: integration-test-log + path: integration.jsonl + if-no-files-found: warn + retention-days: 3 + + # Ungated, and runs regardless of how integration-test ended — deliberately the only job + # with checks:write/pull-requests:write, so those never coexist on a runner with fork + # code. Also the counterpart to the sibling repo's separate finalize-on-incomplete job: + # environment-approval rejection or timeout fails integration-test before any of its own + # steps run (including an artifact upload), which this handles via needs.*.result rather + # than requiring a second job to notice a check stuck at queued. + report: + name: report + needs: [resolve, integration-test] + if: always() && needs.resolve.outputs.found == 'true' + runs-on: ubuntu-latest + timeout-minutes: 5 + permissions: + pull-requests: write + checks: write + steps: + - name: Download integration test log + id: download-log + continue-on-error: true # absent when the job never started (rejected/timed-out approval) + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 + with: + name: integration-test-log + path: integration-test-log + + - name: Report check run result + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 + env: + JOB_RESULT: ${{ needs.integration-test.result }} + PR_SHA: ${{ needs.resolve.outputs.sha }} + PR_NUMBER: ${{ needs.resolve.outputs.number }} + with: + script: | + const fs = require('fs'); + const conclusion = process.env.JOB_RESULT === 'success' ? 'success' : 'failure'; + const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`; + + let table = '_No test output was captured — the job likely did not start (environment approval was rejected or timed out before the run began)._'; + const logPath = 'integration-test-log/integration.jsonl'; + if (fs.existsSync(logPath)) { + const lines = fs.readFileSync(logPath, 'utf8').split('\n'); + const results = []; + let buildFailed = false; + for (const line of lines) { + if (!line) continue; + let event; + try { + event = JSON.parse(line); + } catch (e) { + continue; + } + if (event.Action === 'build-fail') buildFailed = true; + // Only live integration tests (TestLive* — the convention every test in + // integration_test.go follows) show up here; the table reports on those, + // never on raw stdout/response bodies. + if (event.Test && event.Test.startsWith('TestLive') && (event.Action === 'pass' || event.Action === 'fail' || event.Action === 'skip')) { + results.push(event); + } + } + results.sort((a, b) => a.Test.localeCompare(b.Test)); + + if (buildFailed) { + table = '⚠️ Build failed before any tests ran — see the job log for compiler errors.'; + } else if (results.length === 0) { + table = '⚠️ No live integration test (TestLive*) results were produced — see the job log for details.'; + } else { + const passed = results.filter(r => r.Action === 'pass').length; + const failed = results.filter(r => r.Action === 'fail').length; + const skipped = results.filter(r => r.Action === 'skip').length; + const icon = r => r.Action === 'pass' ? '✅ Pass' : r.Action === 'skip' ? '⚠️ Skip' : '❌ Fail'; + const rows = results.map(r => `| ${r.Test} | ${icon(r)} | ${r.Elapsed ?? 0} |`).join('\n'); + table = `**Live integration tests — Total:** ${results.length} | **Passed:** ${passed} | **Failed:** ${failed} | **Skipped:** ${skipped}\n\n` + + `| Test | Result | Duration (s) |\n|---|---|---|\n${rows}`; + } + } + + const checks = await github.rest.checks.listForRef({ + owner: context.repo.owner, + repo: context.repo.repo, + ref: process.env.PR_SHA, + check_name: 'integration-test', + }); + const check = checks.data.check_runs[0]; + if (check) { + await github.rest.checks.update({ + owner: context.repo.owner, + repo: context.repo.repo, + check_run_id: check.id, + status: 'completed', + conclusion, + details_url: runUrl, + }); + } + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: Number(process.env.PR_NUMBER), + body: `### Integration Test: ${conclusion === 'success' ? '✅ Passed' : '❌ Failed'}\n\n[View run](${runUrl})\n\n${table}`, + }); diff --git a/.github/workflows/pr-metadata.yml b/.github/workflows/pr-metadata.yml new file mode 100644 index 0000000..5a97ac4 --- /dev/null +++ b/.github/workflows/pr-metadata.yml @@ -0,0 +1,36 @@ +name: PR Metadata + +# Untrusted trigger: runs for PRs from forks with no secrets and read-only +# permissions. Its only job is to record which PR/SHA to test, so the +# privileged integration-test workflow (triggered via workflow_run) never has +# to check out or execute anything from this workflow's own run context. +on: + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + +permissions: + contents: read + +jobs: + record-pr: + name: record-pr + if: github.event.pull_request.draft == false + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - name: Write PR metadata + run: | + mkdir -p pr-metadata + echo "${{ github.event.pull_request.number }}" > pr-metadata/pr-number + echo "${{ github.event.pull_request.head.sha }}" > pr-metadata/pr-sha + + - name: Upload PR metadata + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: pr-metadata + path: pr-metadata/ + # integration-test.yml's ungated `resolve` job downloads and validates this + # artifact immediately on workflow_run, before any approval wait — so this only + # needs to survive normal workflow_run delivery/retry latency, not the (up to + # 30-day) environment approval SLA. Keeping several days as a rerun buffer. + retention-days: 3 diff --git a/.gitignore b/.gitignore index e7db8af..0a124ce 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,10 @@ coverage.txt test-results.json *.breakdown +integration.jsonl + +# Local credentials for running the live integration suite (make test-integration) +.env.testacc # Dependency directories (remove the comment below to include it) # vendor/ diff --git a/Makefile b/Makefile index 9be5bd7..934dded 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: default all build clean test fmtcheck test-cover +.PHONY: default all build clean test fmtcheck test-cover test-integration # syntheticsclient (v1) is deprecated and excluded from tests, builds, and # coverage; only syntheticsclientv2 is exercised here. @@ -14,7 +14,7 @@ build: fmtcheck clean: @echo "==> Cleaning out old builds " go clean - rm -rf coverage.txt test-results.json v2.breakdown + rm -rf coverage.txt test-results.json v2.breakdown integration.jsonl fmt: @@ -34,3 +34,16 @@ test: fmtcheck test-cover: clean fmtcheck @echo "==> Running all tests with coverage and JSON output" go test $(FILES) -timeout=30s -parallel=8 -json -cover -covermode=atomic -coverprofile coverage.txt > test-results.json + +# Runs the live integration suite (syntheticsclientv2/integration_test.go) against a real +# Synthetics org. Requires API_ACCESS_TOKEN and REALM in the environment. Not part of the +# default test/test-cover targets: it mutates live state and needs real credentials, so it +# only runs where those are deliberately provided (a developer's shell, or the gated +# integration-test CI job). +test-integration: SHELL:=/bin/bash +test-integration: + @echo "==> Running live integration tests" + set -o pipefail; go test -tags=integration -json ./syntheticsclientv2/... -timeout 30m \ + | sed '/X-Sf-Token/d' \ + | tee integration.jsonl \ + | jq -j -r 'if .Action == "output" then .Output else empty end' diff --git a/syntheticsclientv2/integration_test.go b/syntheticsclientv2/integration_test.go index 4d48d0d..d088255 100644 --- a/syntheticsclientv2/integration_test.go +++ b/syntheticsclientv2/integration_test.go @@ -23,56 +23,122 @@ import ( "log" "os" "testing" + "time" ) var ( - token = os.Getenv("API_ACCESS_TOKEN") - realm = os.Getenv("REALM") - getChecksV2Body = `{"testType":"","page":1,"perPage":50,"search":"","orderBy":"id"}` - inputGetChecksV2 = GetChecksV2Options{} - createVariableV2Body = `{"variable":{"description":"beep-var","name":"a-variable-named-foodz","secret":false,"value":"bar"}}` - inputVariableV2Data = VariableV2Input{} - updateVariableV2Body = `{"variable":{"description":"My super awesome test variable22","name":"a-variable-named-foodz","secret":false,"value":"bar"}}` - updateVariableV2Data = VariableV2Input{} - createLocationV2Body = `{"location":{"id":"private-data-center-go-test","label":"Data Center place", "default":false}}` - inputLocationV2Data = LocationV2Input{} - createHttpCheckV2Body = `{"test":{"name":"a-minimal-http-integration-test","type":"http", "automaticRetries": 1, "customProperties": [{"key": "Test_Key", "value": "Test Custom Properties"}],"url":"https://www.splunk.com","locationIds":["aws-us-east-1"],"frequency":10,"schedulingStrategy":"round_robin","active":true,"requestMethod":"GET","body":null,"userAgent":null,"authentication":null,"verifyCertificates":false}}` - inputHttpCheckV2Data = HttpCheckV2Input{} - updateHttpCheckV2Body = `{"test":{"name":"a-maximal-http-integration-test-update","type":"http", "automaticRetries": 1, "customProperties": [{"key": "Test_Key", "value": "Test Custom Properties"}],"url":"https://www.splunk.com/updated","locationIds":["aws-us-east-1","aws-ap-southeast-2","aws-ap-southeast-4"],"frequency":30,"schedulingStrategy":"round_robin","active":true,"requestMethod":"GET","body":null,"headers":[{"name":"header-1","value":"value-1"},{"name":"header_2","value":"value_2"}],"validations":[{"type":"assert_string","actual":"{{response.first_byte_time}}","expected":"100","comparator":"equals"},{"type":"assert_string","actual":"{{headers.Content-Length}}","expected":"100","comparator":"does_not_equal"}],"userAgent":"user-agent_standards met","authentication":{"username":"beepusers","password":"{{env.terraform-test-foo-301}}"},"verifyCertificates":true}}` - updateHttpCheckV2Data = HttpCheckV2Input{} - createMaximalBrowserCheckV2Body = `{"test":{"name":"a-maximal-browser-beep-test", "automaticRetries": 1, "customProperties": [{"key": "Test_Key", "value": "Test Custom Properties"}],"transactions":[{"name":"Synthetic transaction 1","steps":[{"name":"Go to URL","type":"go_to_url","url":"https://splunk.com","action":"go_to_url","options":{"url":"https://splunk.com"}},{"name":"click","type":"click_element","selectors":[{"type":"id","value":"clicky"}],"waitForNav":true,"waitForNavTimeout":2000},{"name":"fill in fieldz","type":"enter_value","selectors":[{"type":"id","value":"beep"}],"value":"{{env.beep-var}}","waitForNav":false,"waitForNavTimeout":50},{"name":"accept---Alert","type":"accept_alert"},{"name":"Select-Val-Index","type":"select_option","selectors":[{"type":"id","value":"selectionz"}],"optionSelectorType":"index","optionSelector":"{{env.beep-var}}","waitForNav":false,"waitForNavTimeout":50},{"name":"Select-val-text","type":"select_option","selectors":[{"type":"id","value":"textzz"}],"optionSelectorType":"text","optionSelector":"sdad","waitForNav":false,"waitForNavTimeout":50},{"name":"Select-Val-Val","type":"select_option","selectors":[{"type":"id","value":"valz"}],"optionSelectorType":"value","optionSelector":"{{env.beep-var}}","waitForNav":false,"waitForNavTimeout":50},{"name":"Run JS","type":"run_javascript","value":"beeeeeeep","waitForNav":true,"waitForNavTimeout":2000},{"name":"Save as text","type":"store_variable_from_element","selectors":[{"type":"link","value":"beepval"}],"variableName":"{{env.terraform-test-foo-301}}"},{"name":"Wait","type":"wait","duration":1312},{"name":"Save JS return Val","type":"store_variable_from_javascript","value":"sdasds","variableName":"{{env.terraform-test-foo-301}}","waitForNav":true,"waitForNavTimeout":2000}]}],"urlProtocol":"https://","startUrl":"www.splunk.com","locationIds":["aws-us-east-1"],"deviceId":1,"frequency":5,"schedulingStrategy":"round_robin","active":true,"advancedSettings":{"verifyCertificates":true,"authentication":{"username":"boopuser","password":"{{env.beep-var}}"},"headers":[{"name":"batman","value":"Agentoz","domain":"www.batmansagent.com"}],"cookies":[{"key":"super","value":"duper","domain":"www.batmansagent.com","path":"/boom/goes/beep"}]}}}` - createMinimalBrowserCheckV2Body = `{"test":{"name":"a-minimal-browser-beep-test", "automaticRetries": 1, "customProperties": [{"key": "Test_Key", "value": "Test Custom Properties"}],"transactions":[{"name":"Synthetic transaction 1","steps":[{"name":"Go to URL","type":"go_to_url","url":"https://splunk.com","action":"go_to_url"}]}],"locationIds":["aws-us-east-1"],"deviceId":1,"frequency":5,"schedulingStrategy":"round_robin","active":true,"advancedSettings":{"verifyCertificates":true}}}` - inputBrowserCheckV2Data = BrowserCheckV2Input{} - updateBrowserCheckV2Body = `{"test":{"name":"a-browser-beep-test", "automaticRetries": 1, "customProperties": [{"key": "Test_Key", "value": "Test Custom Properties"}],"transactions":[{"name":"Synthetic transaction 1","steps":[{"name":"Go to URL","type":"go_to_url","url":"https://splunk.com","action":"go_to_url","options":{"url":"https://splunk.com"}},{"name":"click","type":"click_element","selectors":[{"type":"id","value":"clicky"}],"waitForNav":true,"waitForNavTimeout":2000},{"name":"fill in fieldz","type":"enter_value","selectors":[{"type":"id","value":"beep"}],"value":"{{env.beep-var}}","waitForNav":false,"waitForNavTimeout":50},{"name":"accept---Alert","type":"accept_alert"},{"name":"Select-Val-Index","type":"select_option","selectors":[{"type":"id","value":"selectionz"}],"optionSelectorType":"index","optionSelector":"{{env.beep-var}}","waitForNav":false,"waitForNavTimeout":50},{"name":"Select-val-text","type":"select_option","selectors":[{"type":"id","value":"textzz"}],"optionSelectorType":"text","optionSelector":"sdad","waitForNav":false,"waitForNavTimeout":50},{"name":"Select-Val-Val","type":"select_option","selectors":[{"type":"id","value":"valz"}],"optionSelectorType":"value","optionSelector":"{{env.beep-var}}","waitForNav":false,"waitForNavTimeout":50},{"name":"Run JS","type":"run_javascript","value":"beeeeeeep","waitForNav":true,"waitForNavTimeout":2000},{"name":"Save as text","type":"store_variable_from_element","selectors":[{"type":"link","value":"beepval"}],"variableName":"{{env.terraform-test-foo-301}}"},{"name":"Save JS return Val","type":"store_variable_from_javascript","value":"sdasds","variableName":"{{env.terraform-test-foo-301}}","waitForNav":true,"waitForNavTimeout":2000}]}],"urlProtocol":"https://","startUrl":"www.splunk.com","locationIds":["aws-us-east-1"],"deviceId":1,"frequency":15,"schedulingStrategy":"round_robin","active":true,"advancedSettings":{"verifyCertificates":true,"authentication":{"username":"boopuser","password":"{{env.beep-var}}"},"headers":[{"name":"batman","value":"Agentoz","domain":"www.batmansagent.com"}],"cookies":[{"key":"super","value":"dooper","domain":"www.batmansagent.com","path":"/boom/goes/beep"}]}}}` - updateBrowserCheckV2Data = BrowserCheckV2Input{} - createPortCheckV2Body = `{"test":{"name":"a - port 443 check","type":"port", "automaticRetries": 1, "customProperties": [{"key": "Test_Key", "value": "Test Custom Properties"}],"url":"","port":443,"protocol":"tcp","host":"www.splunk.com","locationIds":["aws-us-east-1"],"frequency":10,"schedulingStrategy":"round_robin","active":true}}` - inputPortCheckV2Data = PortCheckV2Input{} - updatePortCheckV2Body = `{"test":{"name":"a2 - port 443 check","type":"port", "automaticRetries": 1, "customProperties": [{"key": "Test_Key", "value": "Test Custom Properties"}],"url":"","port":448,"protocol":"tcp","host":"www.splunk.com","locationIds":["aws-us-east-1"],"frequency":10,"schedulingStrategy":"round_robin","active":true}}` - updatePortCheckV2Data = PortCheckV2Input{} - createApiV2Body = `{"test":{"active":true,"deviceId":1,"frequency":5, "automaticRetries": 1, "customProperties": [{"key": "Test_Key", "value": "Test Custom Properties"}],"locationIds":["aws-us-east-1"],"name":"a-maximual-API-boop-test","schedulingStrategy":"round_robin","requests":[{"configuration":{"name":"Get-Test","requestMethod":"GET","url":"https://api.us1.signalfx.com/v2/synthetics/v2/tests/api/489","headers":{"X-SF-TOKEN":"jinglebellsbatmanshells","beep":"boop"},"body":null},"setup":[{"name":"Extract from response body","type":"extract_json","source":"{{response.body}}","extractor":"sd","variable":"extractsetupvar"},{"name":"JavaScript run","type":"javascript","code":"asdasd","variable":"jsvarsetup"},{"name":"Save response body","type":"save","value":"{{response.body}}","variable":"savesetupvar"}],"validations":[{"name":"JavaScript run","type":"javascript","code":"codetorun","variable":"jscodevar"},{"name":"Save response body","type":"save","value":"{{response.body}}","variable":"saverespvar"},{"name":"Assert response code equals 200","type":"assert_numeric","actual":"{{response.code}}","expected":"200","comparator":"equals"},{"name":"Extract from response body","type":"extract_json","source":"{{response.body}}","extractor":"js.extractor","variable":"extractjvar"}]}]}}` - createMinimalApiV2Body = `{"test":{"active":true,"deviceId":1,"frequency":5, "automaticRetries": 1, "customProperties": [{"key": "Test_Key", "value": "Test Custom Properties"}],"locationIds":["aws-us-east-1"],"name":"a-minimal-API-boop-test","schedulingStrategy":"round_robin","requests":[{"configuration":{"name":"apishortGet-Test","requestMethod":"GET","url":"https://api.us1.signalfx.com/v2/synthetics/v2/tests/api/489"}}]}}` - inputApiCheckV2Data = ApiCheckV2Input{} - updateApiCheckV2Body = `{"test":{"active":true,"deviceId":1,"frequency":5, "automaticRetries": 1, "customProperties": [{"key": "Test_Key", "value": "Test Custom Properties"}],"locationIds":["aws-us-east-1"],"name":"a-API-boop-test","schedulingStrategy":"round_robin","requests":[{"configuration":{"name":"Get-Test","requestMethod": "GET","url":"https://api.us1.signalfx.com/v2/synthetics/v2/tests/api/4892","headers":{"X-SF-TOKEN":"jinglebellsbatmanshells", "beep":"boop"},"body":null},"setup":[{"name":"Extract from response body","type":"extract_json","source":"{{response.body}}","extractor":"$.requests","variable":"custom-varz"}],"validations":[{"name":"Assert response code equals 200","type":"assert_numeric","actual":"{{response.code}}","expected":"200","comparator":"equals"}]}]}}` - updateApiCheckV2Data = ApiCheckV2Input{} - inputDowntimeConfigurationV2Data = DowntimeConfigurationV2Input{} - updateDowntimeConfigurationV2Data = DowntimeConfigurationV2Input{} + token = os.Getenv("API_ACCESS_TOKEN") + realm = os.Getenv("REALM") + liveGetChecksV2Body = `{"testType":"","page":1,"perPage":50,"search":"","orderBy":"id"}` + liveInputGetChecksV2 = GetChecksV2Options{} + liveCreateVariableV2Body = `{"variable":{"description":"beep-var","name":"a-variable-named-foodz","secret":false,"value":"bar"}}` + liveInputVariableV2Data = VariableV2Input{} + liveUpdateVariableV2Body = `{"variable":{"description":"My super awesome test variable22","name":"a-variable-named-foodz","secret":false,"value":"bar"}}` + updateVariableV2Data = VariableV2Input{} + liveCreateLocationV2Body = `{"location":{"id":"private-data-center-go-test","label":"Data Center place", "default":false}}` + liveInputLocationV2Data = LocationV2Input{} + liveCreateHttpCheckV2Body = `{"test":{"name":"a-minimal-http-integration-test","type":"http", "automaticRetries": 1, "customProperties": [{"key": "Test_Key", "value": "Test Custom Properties"}],"url":"https://www.splunk.com","locationIds":["aws-us-east-1"],"frequency":10,"schedulingStrategy":"round_robin","active":true,"requestMethod":"GET","body":null,"userAgent":null,"authentication":null,"verifyCertificates":false}}` + liveInputHttpCheckV2Data = HttpCheckV2Input{} + liveUpdateHttpCheckV2Body = `{"test":{"name":"a-maximal-http-integration-test-update","type":"http", "automaticRetries": 1, "customProperties": [{"key": "Test_Key", "value": "Test Custom Properties"}],"url":"https://www.splunk.com/updated","locationIds":["aws-us-east-1","aws-ap-southeast-2","aws-ap-southeast-4"],"frequency":30,"schedulingStrategy":"round_robin","active":true,"requestMethod":"GET","body":null,"headers":[{"name":"header-1","value":"value-1"},{"name":"header_2","value":"value_2"}],"validations":[{"type":"assert_string","actual":"{{response.first_byte_time}}","expected":"100","comparator":"equals"},{"type":"assert_string","actual":"{{headers.Content-Length}}","expected":"100","comparator":"does_not_equal"}],"userAgent":"user-agent_standards met","authentication":{"username":"beepusers","password":"{{env.terraform-test-foo-301}}"},"verifyCertificates":true}}` + updateHttpCheckV2Data = HttpCheckV2Input{} + createMaximalBrowserCheckV2Body = `{"test":{"name":"a-maximal-browser-beep-test", "automaticRetries": 1, "customProperties": [{"key": "Test_Key", "value": "Test Custom Properties"}],"transactions":[{"name":"Synthetic transaction 1","steps":[{"name":"Go to URL","type":"go_to_url","url":"https://splunk.com","action":"go_to_url","options":{"url":"https://splunk.com"}},{"name":"click","type":"click_element","selectors":[{"type":"id","value":"clicky"}],"waitForNav":true,"waitForNavTimeout":2000},{"name":"fill in fieldz","type":"enter_value","selectors":[{"type":"id","value":"beep"}],"value":"{{env.beep-var}}","waitForNav":false,"waitForNavTimeout":50},{"name":"accept---Alert","type":"accept_alert"},{"name":"Select-Val-Index","type":"select_option","selectors":[{"type":"id","value":"selectionz"}],"optionSelectorType":"index","optionSelector":"{{env.beep-var}}","waitForNav":false,"waitForNavTimeout":50},{"name":"Select-val-text","type":"select_option","selectors":[{"type":"id","value":"textzz"}],"optionSelectorType":"text","optionSelector":"sdad","waitForNav":false,"waitForNavTimeout":50},{"name":"Select-Val-Val","type":"select_option","selectors":[{"type":"id","value":"valz"}],"optionSelectorType":"value","optionSelector":"{{env.beep-var}}","waitForNav":false,"waitForNavTimeout":50},{"name":"Run JS","type":"run_javascript","value":"beeeeeeep","waitForNav":true,"waitForNavTimeout":2000},{"name":"Save as text","type":"store_variable_from_element","selectors":[{"type":"link","value":"beepval"}],"variableName":"{{env.terraform-test-foo-301}}"},{"name":"Wait","type":"wait","duration":1312},{"name":"Save JS return Val","type":"store_variable_from_javascript","value":"sdasds","variableName":"{{env.terraform-test-foo-301}}","waitForNav":true,"waitForNavTimeout":2000}]}],"urlProtocol":"https://","startUrl":"www.splunk.com","locationIds":["aws-us-east-1"],"deviceId":1,"frequency":5,"schedulingStrategy":"round_robin","active":true,"advancedSettings":{"verifyCertificates":true,"authentication":{"username":"boopuser","password":"{{env.beep-var}}"},"headers":[{"name":"batman","value":"Agentoz","domain":"www.batmansagent.com"}],"cookies":[{"key":"super","value":"duper","domain":"www.batmansagent.com","path":"/boom/goes/beep"}]}}}` + createMinimalBrowserCheckV2Body = `{"test":{"name":"a-minimal-browser-beep-test", "automaticRetries": 1, "customProperties": [{"key": "Test_Key", "value": "Test Custom Properties"}],"transactions":[{"name":"Synthetic transaction 1","steps":[{"name":"Go to URL","type":"go_to_url","url":"https://splunk.com","action":"go_to_url"}]}],"locationIds":["aws-us-east-1"],"deviceId":1,"frequency":5,"schedulingStrategy":"round_robin","active":true,"advancedSettings":{"verifyCertificates":true}}}` + liveInputBrowserCheckV2Data = BrowserCheckV2Input{} + liveUpdateBrowserCheckV2Body = `{"test":{"name":"a-browser-beep-test", "automaticRetries": 1, "customProperties": [{"key": "Test_Key", "value": "Test Custom Properties"}],"transactions":[{"name":"Synthetic transaction 1","steps":[{"name":"Go to URL","type":"go_to_url","url":"https://splunk.com","action":"go_to_url","options":{"url":"https://splunk.com"}},{"name":"click","type":"click_element","selectors":[{"type":"id","value":"clicky"}],"waitForNav":true,"waitForNavTimeout":2000},{"name":"fill in fieldz","type":"enter_value","selectors":[{"type":"id","value":"beep"}],"value":"{{env.beep-var}}","waitForNav":false,"waitForNavTimeout":50},{"name":"accept---Alert","type":"accept_alert"},{"name":"Select-Val-Index","type":"select_option","selectors":[{"type":"id","value":"selectionz"}],"optionSelectorType":"index","optionSelector":"{{env.beep-var}}","waitForNav":false,"waitForNavTimeout":50},{"name":"Select-val-text","type":"select_option","selectors":[{"type":"id","value":"textzz"}],"optionSelectorType":"text","optionSelector":"sdad","waitForNav":false,"waitForNavTimeout":50},{"name":"Select-Val-Val","type":"select_option","selectors":[{"type":"id","value":"valz"}],"optionSelectorType":"value","optionSelector":"{{env.beep-var}}","waitForNav":false,"waitForNavTimeout":50},{"name":"Run JS","type":"run_javascript","value":"beeeeeeep","waitForNav":true,"waitForNavTimeout":2000},{"name":"Save as text","type":"store_variable_from_element","selectors":[{"type":"link","value":"beepval"}],"variableName":"{{env.terraform-test-foo-301}}"},{"name":"Save JS return Val","type":"store_variable_from_javascript","value":"sdasds","variableName":"{{env.terraform-test-foo-301}}","waitForNav":true,"waitForNavTimeout":2000}]}],"urlProtocol":"https://","startUrl":"www.splunk.com","locationIds":["aws-us-east-1"],"deviceId":1,"frequency":15,"schedulingStrategy":"round_robin","active":true,"advancedSettings":{"verifyCertificates":true,"authentication":{"username":"boopuser","password":"{{env.beep-var}}"},"headers":[{"name":"batman","value":"Agentoz","domain":"www.batmansagent.com"}],"cookies":[{"key":"super","value":"dooper","domain":"www.batmansagent.com","path":"/boom/goes/beep"}]}}}` + updateBrowserCheckV2Data = BrowserCheckV2Input{} + liveCreatePortCheckV2Body = `{"test":{"name":"a - port 443 check","type":"port", "automaticRetries": 1, "customProperties": [{"key": "Test_Key", "value": "Test Custom Properties"}],"url":"","port":443,"protocol":"tcp","host":"www.splunk.com","locationIds":["aws-us-east-1"],"frequency":10,"schedulingStrategy":"round_robin","active":true}}` + liveInputPortCheckV2Data = PortCheckV2Input{} + liveUpdatePortCheckV2Body = `{"test":{"name":"a2 - port 443 check","type":"port", "automaticRetries": 1, "customProperties": [{"key": "Test_Key", "value": "Test Custom Properties"}],"url":"","port":448,"protocol":"tcp","host":"www.splunk.com","locationIds":["aws-us-east-1"],"frequency":10,"schedulingStrategy":"round_robin","active":true}}` + updatePortCheckV2Data = PortCheckV2Input{} + liveCreateApiV2Body = `{"test":{"active":true,"deviceId":1,"frequency":5, "automaticRetries": 1, "customProperties": [{"key": "Test_Key", "value": "Test Custom Properties"}],"locationIds":["aws-us-east-1"],"name":"a-maximual-API-boop-test","schedulingStrategy":"round_robin","requests":[{"configuration":{"name":"Get-Test","requestMethod":"GET","url":"https://api.us1.signalfx.com/v2/synthetics/v2/tests/api/489","headers":{"X-SF-TOKEN":"jinglebellsbatmanshells","beep":"boop"},"body":null},"setup":[{"name":"Extract from response body","type":"extract_json","source":"{{response.body}}","extractor":"sd","variable":"extractsetupvar"},{"name":"JavaScript run","type":"javascript","code":"asdasd","variable":"jsvarsetup"},{"name":"Save response body","type":"save","value":"{{response.body}}","variable":"savesetupvar"}],"validations":[{"name":"JavaScript run","type":"javascript","code":"codetorun","variable":"jscodevar"},{"name":"Save response body","type":"save","value":"{{response.body}}","variable":"saverespvar"},{"name":"Assert response code equals 200","type":"assert_numeric","actual":"{{response.code}}","expected":"200","comparator":"equals"},{"name":"Extract from response body","type":"extract_json","source":"{{response.body}}","extractor":"js.extractor","variable":"extractjvar"}]}]}}` + createMinimalApiV2Body = `{"test":{"active":true,"deviceId":1,"frequency":5, "automaticRetries": 1, "customProperties": [{"key": "Test_Key", "value": "Test Custom Properties"}],"locationIds":["aws-us-east-1"],"name":"a-minimal-API-boop-test","schedulingStrategy":"round_robin","requests":[{"configuration":{"name":"apishortGet-Test","requestMethod":"GET","url":"https://api.us1.signalfx.com/v2/synthetics/v2/tests/api/489"}}]}}` + inputApiCheckV2Data = ApiCheckV2Input{} + liveUpdateApiCheckV2Body = `{"test":{"active":true,"deviceId":1,"frequency":5, "automaticRetries": 1, "customProperties": [{"key": "Test_Key", "value": "Test Custom Properties"}],"locationIds":["aws-us-east-1"],"name":"a-API-boop-test","schedulingStrategy":"round_robin","requests":[{"configuration":{"name":"Get-Test","requestMethod": "GET","url":"https://api.us1.signalfx.com/v2/synthetics/v2/tests/api/4892","headers":{"X-SF-TOKEN":"jinglebellsbatmanshells", "beep":"boop"},"body":null},"setup":[{"name":"Extract from response body","type":"extract_json","source":"{{response.body}}","extractor":"$.requests","variable":"custom-varz"}],"validations":[{"name":"Assert response code equals 200","type":"assert_numeric","actual":"{{response.code}}","expected":"200","comparator":"equals"}]}]}}` + updateApiCheckV2Data = ApiCheckV2Input{} + liveInputDowntimeConfigurationV2Data = DowntimeConfigurationV2Input{} + updateDowntimeConfigurationV2Data = DowntimeConfigurationV2Input{} ) -// You will need to fill in values for the get and delete tests -// as the check ids will vary from organization to organization +// Every test below creates whatever fixture it needs and deletes it before +// returning, so the suite can be re-run repeatedly against the same org +// without accumulating orphaned resources or colliding on fixed names/ids. + +const ( + livePrereqBeepVarName = "beep-var" + livePrereqTerraformVarName = "terraform-test-foo-301" +) + +// TestMain provisions the two variables referenced by name +// (via {{env.beep-var}} and {{env.terraform-test-foo-301}}) in the http/browser +// check bodies used below, since the Synthetics API requires an +// authentication.password variable reference to resolve to an existing +// variable. Both are torn down after the suite runs. +func TestMain(m *testing.M) { + c := NewClient(token, realm) + + // A prior run killed before m.Run() returned (e.g. a cancelled CI job) skips + // both t.Cleanup and the deletes below, leaving these fixed-name variables + // behind. Reclaim any leftovers before creating fresh ones so that run isn't + // stuck failing every subsequent run with a duplicate-name error. + reclaimPrerequisiteVariable(c, livePrereqBeepVarName) + reclaimPrerequisiteVariable(c, livePrereqTerraformVarName) + + beepVar, _, err := c.CreateVariableV2(&VariableV2Input{Variable: Variable{Name: livePrereqBeepVarName, Value: "bar", Secret: false}}) + if err != nil { + log.Fatalf("failed to create prerequisite variable %q: %v", livePrereqBeepVarName, err) + } + + terraformVar, _, err := c.CreateVariableV2(&VariableV2Input{Variable: Variable{Name: livePrereqTerraformVarName, Value: "bar", Secret: false}}) + if err != nil { + if _, delErr := c.DeleteVariableV2(beepVar.Variable.ID); delErr != nil { + log.Printf("failed to clean up prerequisite variable %q: %v", livePrereqBeepVarName, delErr) + } + log.Fatalf("failed to create prerequisite variable %q: %v", livePrereqTerraformVarName, err) + } + + code := m.Run() + + if _, err := c.DeleteVariableV2(beepVar.Variable.ID); err != nil { + log.Printf("failed to clean up prerequisite variable %q: %v", livePrereqBeepVarName, err) + } + if _, err := c.DeleteVariableV2(terraformVar.Variable.ID); err != nil { + log.Printf("failed to clean up prerequisite variable %q: %v", livePrereqTerraformVarName, err) + } + + os.Exit(code) +} + +// reclaimPrerequisiteVariable deletes any existing variable with the given name so a +// stale leftover from an interrupted prior run doesn't cause CreateVariableV2 below to +// fail with a duplicate-name error. +func reclaimPrerequisiteVariable(c *Client, name string) { + existing, _, err := c.GetVariablesV2() + if err != nil { + log.Printf("failed to list variables while checking for leftover %q: %v", name, err) + return + } + for _, v := range existing.Variable { + if v.Name != name { + continue + } + if _, err := c.DeleteVariableV2(v.ID); err != nil { + log.Printf("failed to reclaim leftover prerequisite variable %q: %v", name, err) + } + } +} func TestLiveGetChecksV2(t *testing.T) { //Create your client with the token c := NewClient(token, realm) - err := json.Unmarshal([]byte(getChecksV2Body), &inputGetChecksV2) + err := json.Unmarshal([]byte(liveGetChecksV2Body), &liveInputGetChecksV2) if err != nil { t.Fatal(err) } // Make the request with your check settings and print result - res, _, err := c.GetChecksV2(&inputGetChecksV2) + res, _, err := c.GetChecksV2(&liveInputGetChecksV2) if err != nil { fmt.Println(err) } else { @@ -87,7 +153,7 @@ func TestLiveGetChecksV2(t *testing.T) { func TestLiveCreateVariableV2(t *testing.T) { - err := json.Unmarshal([]byte(createVariableV2Body), &inputVariableV2Data) + err := json.Unmarshal([]byte(liveCreateVariableV2Body), &liveInputVariableV2Data) if err != nil { t.Fatal(err) } @@ -95,47 +161,53 @@ func TestLiveCreateVariableV2(t *testing.T) { //Create your client with the token c := NewClient(token, realm) - fmt.Println(inputVariableV2Data) + fmt.Println(liveInputVariableV2Data) // Make the request with your check settings and print result - res, reqDetail, err := c.CreateVariableV2(&inputVariableV2Data) - if err != nil { - fmt.Println(err) - } else { - fmt.Println(reqDetail) - JsonPrint(res) - } - + res, reqDetail, err := c.CreateVariableV2(&liveInputVariableV2Data) if err != nil { t.Fatal(err) } + fmt.Println(reqDetail) + JsonPrint(res) + + t.Cleanup(func() { + if _, err := c.DeleteVariableV2(res.Variable.ID); err != nil { + t.Errorf("failed to clean up variable %d: %v", res.Variable.ID, err) + } + }) } func TestLiveUpdateVariableV2(t *testing.T) { - err := json.Unmarshal([]byte(updateVariableV2Body), &updateVariableV2Data) + //Create your client with the token + c := NewClient(token, realm) + + created, _, err := c.CreateVariableV2(&VariableV2Input{Variable: Variable{Name: "a-variable-named-foodz", Value: "bar", Secret: false}}) if err != nil { t.Fatal(err) } + t.Cleanup(func() { + if _, err := c.DeleteVariableV2(created.Variable.ID); err != nil { + t.Errorf("failed to clean up variable %d: %v", created.Variable.ID, err) + } + }) - //Update your client with the token - c := NewClient(token, realm) + err = json.Unmarshal([]byte(liveUpdateVariableV2Body), &updateVariableV2Data) + if err != nil { + t.Fatal(err) + } fmt.Println(updateVariableV2Data) // Make the request with your check settings and print result - res, reqDetail, err := c.UpdateVariableV2(859, &updateVariableV2Data) - if err != nil { - fmt.Println(err) - } else { - fmt.Println(reqDetail) - JsonPrint(res) - } - + res, reqDetail, err := c.UpdateVariableV2(created.Variable.ID, &updateVariableV2Data) if err != nil { t.Fatal(err) } + fmt.Println(reqDetail) + JsonPrint(res) } @@ -144,17 +216,22 @@ func TestLiveGetVariableV2(t *testing.T) { //Create your client with the token c := NewClient(token, realm) - // Make the request with your check settings and print result - res, _, err := c.GetVariableV2(859) + created, _, err := c.CreateVariableV2(&VariableV2Input{Variable: Variable{Name: "a-variable-named-foodz", Value: "bar", Secret: false}}) if err != nil { - fmt.Println(err) - } else { - JsonPrint(res) + t.Fatal(err) } + t.Cleanup(func() { + if _, err := c.DeleteVariableV2(created.Variable.ID); err != nil { + t.Errorf("failed to clean up variable %d: %v", created.Variable.ID, err) + } + }) + // Make the request with your check settings and print result + res, _, err := c.GetVariableV2(created.Variable.ID) if err != nil { t.Fatal(err) } + JsonPrint(res) } @@ -163,17 +240,17 @@ func TestLiveDeleteVariableV2(t *testing.T) { //Create your client with the token c := NewClient(token, realm) - // Make the request with your check settings and print result - res, err := c.DeleteVariableV2(398) + created, _, err := c.CreateVariableV2(&VariableV2Input{Variable: Variable{Name: "a-variable-named-foodz", Value: "bar", Secret: false}}) if err != nil { - fmt.Println(err) - } else { - JsonPrint(res) + t.Fatal(err) } + // Make the request with your check settings and print result + res, err := c.DeleteVariableV2(created.Variable.ID) if err != nil { t.Fatal(err) } + JsonPrint(res) } @@ -201,12 +278,17 @@ func TestLiveHttpCheckCreateUpdateDeleteV2(t *testing.T) { c := NewClient(token, realm) var err error - checkId, err := CreateHttpCheckV2(createHttpCheckV2Body, c) + checkId, err := CreateHttpCheckV2(liveCreateHttpCheckV2Body, c) if err != nil { t.Fatal(err) } + t.Cleanup(func() { + if err := DeleteHttpCheckV2(checkId, c); err != nil { + t.Errorf("failed to clean up http check %d: %v", checkId, err) + } + }) - err = UpdateHttpCheckV2(checkId, updateHttpCheckV2Body, c) + err = UpdateHttpCheckV2(checkId, liveUpdateHttpCheckV2Body, c) if err != nil { t.Fatal(err) } @@ -216,7 +298,7 @@ func TestLiveHttpCheckCreateUpdateDeleteV2(t *testing.T) { t.Fatal(err) } - err2 := UpdateHttpCheckV2(checkId, createHttpCheckV2Body, c) + err2 := UpdateHttpCheckV2(checkId, liveCreateHttpCheckV2Body, c) if err2 != nil { t.Fatal(err2) } @@ -226,47 +308,42 @@ func TestLiveHttpCheckCreateUpdateDeleteV2(t *testing.T) { t.Fatal(err) } - err = DeleteHttpCheckV2(checkId, c) - if err != nil { - t.Fatal(err) - } - } func CreateHttpCheckV2(test string, c *Client) (int, error) { - err := json.Unmarshal([]byte(test), &inputHttpCheckV2Data) + err := json.Unmarshal([]byte(test), &liveInputHttpCheckV2Data) if err != nil { return 0, err } // Make the request with your check settings and print result - res, reqDetail, err := c.CreateHttpCheckV2(&inputHttpCheckV2Data) + res, reqDetail, err := c.CreateHttpCheckV2(&liveInputHttpCheckV2Data) if err != nil { return 0, err } fmt.Printf("\nReq was: \n%v\n", reqDetail) JsonPrint(res) - inputHttpCheckV2Data = HttpCheckV2Input{} + liveInputHttpCheckV2Data = HttpCheckV2Input{} return res.Test.ID, nil } func UpdateHttpCheckV2(checkId int, test string, c *Client) error { - err := json.Unmarshal([]byte(test), &inputHttpCheckV2Data) + err := json.Unmarshal([]byte(test), &liveInputHttpCheckV2Data) if err != nil { return err } // Make the request with your check settings and print result - res, reqDetail, err := c.UpdateHttpCheckV2(checkId, &inputHttpCheckV2Data) + res, reqDetail, err := c.UpdateHttpCheckV2(checkId, &liveInputHttpCheckV2Data) if err != nil { return err } fmt.Printf("\nReq was: \n%v\n", reqDetail) JsonPrint(res) - inputHttpCheckV2Data = HttpCheckV2Input{} + liveInputHttpCheckV2Data = HttpCheckV2Input{} return nil } @@ -301,12 +378,18 @@ func TestLiveCreateHttpCheckV2(t *testing.T) { //Create your client with the token c := NewClient(token, realm) - checkId, err := CreateHttpCheckV2(createHttpCheckV2Body, c) + checkId, err := CreateHttpCheckV2(liveCreateHttpCheckV2Body, c) if err != nil { t.Fatal(err) } log.Printf("Http Check ID: %d", checkId) + t.Cleanup(func() { + if err := DeleteHttpCheckV2(checkId, c); err != nil { + t.Errorf("failed to clean up http check %d: %v", checkId, err) + } + }) + } func TestLiveUpdateHttpCheckV2(t *testing.T) { @@ -314,7 +397,17 @@ func TestLiveUpdateHttpCheckV2(t *testing.T) { //Create your client with the token c := NewClient(token, realm) - err := UpdateHttpCheckV2(1111, updateHttpCheckV2Body, c) + checkId, err := CreateHttpCheckV2(liveCreateHttpCheckV2Body, c) + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + if err := DeleteHttpCheckV2(checkId, c); err != nil { + t.Errorf("failed to clean up http check %d: %v", checkId, err) + } + }) + + err = UpdateHttpCheckV2(checkId, liveUpdateHttpCheckV2Body, c) if err != nil { t.Fatal(err) } @@ -326,7 +419,17 @@ func TestLiveGetHttpCheckV2(t *testing.T) { //Create your client with the token c := NewClient(token, realm) - err := GetHttpCheckV2(1111, c) + checkId, err := CreateHttpCheckV2(liveCreateHttpCheckV2Body, c) + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + if err := DeleteHttpCheckV2(checkId, c); err != nil { + t.Errorf("failed to clean up http check %d: %v", checkId, err) + } + }) + + err = GetHttpCheckV2(checkId, c) if err != nil { t.Fatal(err) } @@ -338,7 +441,12 @@ func TestLiveDeleteHttpCheckV2(t *testing.T) { //Create your client with the token c := NewClient(token, realm) - err := DeleteHttpCheckV2(1111, c) + checkId, err := CreateHttpCheckV2(liveCreateHttpCheckV2Body, c) + if err != nil { + t.Fatal(err) + } + + err = DeleteHttpCheckV2(checkId, c) if err != nil { t.Fatal(err) } @@ -355,6 +463,11 @@ func TestLiveBrowserCheckCreateUpdateAndDeleteV2(t *testing.T) { if err != nil { t.Fatal(err) } + t.Cleanup(func() { + if err := DeleteBrowserCheckV2(checkId, c); err != nil { + t.Errorf("failed to clean up browser check %d: %v", checkId, err) + } + }) err = UpdateBrowserCheckV2(checkId, createMinimalBrowserCheckV2Body, c) if err != nil { @@ -376,22 +489,17 @@ func TestLiveBrowserCheckCreateUpdateAndDeleteV2(t *testing.T) { t.Fatal(err) } - err = DeleteBrowserCheckV2(checkId, c) - if err != nil { - t.Fatal(err) - } - } func CreateBrowserCheckV2(test string, c *Client) (int, error) { - err := json.Unmarshal([]byte(test), &inputBrowserCheckV2Data) + err := json.Unmarshal([]byte(test), &liveInputBrowserCheckV2Data) if err != nil { return 0, err } // Make the request with your check settings and print result - res, reqDetail, err := c.CreateBrowserCheckV2(&inputBrowserCheckV2Data) + res, reqDetail, err := c.CreateBrowserCheckV2(&liveInputBrowserCheckV2Data) if err != nil { return 0, err } @@ -451,6 +559,12 @@ func TestLiveCreateBrowserCheckV2(t *testing.T) { } log.Printf("Browser Check ID: %d", checkId) + + t.Cleanup(func() { + if err := DeleteBrowserCheckV2(checkId, c); err != nil { + t.Errorf("failed to clean up browser check %d: %v", checkId, err) + } + }) } func TestLiveGetBrowserCheckV2(t *testing.T) { @@ -458,7 +572,17 @@ func TestLiveGetBrowserCheckV2(t *testing.T) { //Create your client with the token c := NewClient(token, realm) - err := GetBrowserCheckV2(1111, c) + checkId, err := CreateBrowserCheckV2(createMaximalBrowserCheckV2Body, c) + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + if err := DeleteBrowserCheckV2(checkId, c); err != nil { + t.Errorf("failed to clean up browser check %d: %v", checkId, err) + } + }) + + err = GetBrowserCheckV2(checkId, c) if err != nil { t.Fatal(err) } @@ -470,7 +594,17 @@ func TestLiveUpdateBrowserCheckV2(t *testing.T) { //Create your client with the token c := NewClient(token, realm) - err := UpdateBrowserCheckV2(1111, createMinimalBrowserCheckV2Body, c) + checkId, err := CreateBrowserCheckV2(createMaximalBrowserCheckV2Body, c) + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + if err := DeleteBrowserCheckV2(checkId, c); err != nil { + t.Errorf("failed to clean up browser check %d: %v", checkId, err) + } + }) + + err = UpdateBrowserCheckV2(checkId, createMinimalBrowserCheckV2Body, c) if err != nil { t.Fatal(err) } @@ -482,7 +616,12 @@ func TestLiveDeleteBrowserCheckV2(t *testing.T) { //Create your client with the token c := NewClient(token, realm) - err := DeleteBrowserCheckV2(1111, c) + checkId, err := CreateBrowserCheckV2(createMaximalBrowserCheckV2Body, c) + if err != nil { + t.Fatal(err) + } + + err = DeleteBrowserCheckV2(checkId, c) if err != nil { t.Fatal(err) } @@ -553,10 +692,15 @@ func TestLiveApiCheckCreateUpdateAndDeleteV2(t *testing.T) { c := NewClient(token, realm) var err error - checkId, err := CreateApiCheckV2(createApiV2Body, c) + checkId, err := CreateApiCheckV2(liveCreateApiV2Body, c) if err != nil { t.Fatal(err) } + t.Cleanup(func() { + if err := DeleteApiCheckV2(checkId, c); err != nil { + t.Errorf("failed to clean up api check %d: %v", checkId, err) + } + }) err = UpdateApiCheckV2(checkId, createMinimalApiV2Body, c) if err != nil { @@ -568,7 +712,7 @@ func TestLiveApiCheckCreateUpdateAndDeleteV2(t *testing.T) { t.Fatal(err) } - err2 := UpdateApiCheckV2(checkId, createApiV2Body, c) + err2 := UpdateApiCheckV2(checkId, liveCreateApiV2Body, c) if err2 != nil { t.Fatal(err2) } @@ -578,23 +722,24 @@ func TestLiveApiCheckCreateUpdateAndDeleteV2(t *testing.T) { t.Fatal(err) } - err = DeleteApiCheckV2(checkId, c) - if err != nil { - t.Fatal(err) - } - } func TestLiveCreateApiCheckV2(t *testing.T) { //Create your client with the token c := NewClient(token, realm) - checkId, err := CreateApiCheckV2(createApiV2Body, c) + checkId, err := CreateApiCheckV2(liveCreateApiV2Body, c) if err != nil { t.Fatal(err) } log.Printf("Api Check ID: %d", checkId) + + t.Cleanup(func() { + if err := DeleteApiCheckV2(checkId, c); err != nil { + t.Errorf("failed to clean up api check %d: %v", checkId, err) + } + }) } func TestLiveGetApiCheckV2(t *testing.T) { @@ -602,7 +747,17 @@ func TestLiveGetApiCheckV2(t *testing.T) { //Create your client with the token c := NewClient(token, realm) - err := GetApiCheckV2(1111, c) + checkId, err := CreateApiCheckV2(liveCreateApiV2Body, c) + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + if err := DeleteApiCheckV2(checkId, c); err != nil { + t.Errorf("failed to clean up api check %d: %v", checkId, err) + } + }) + + err = GetApiCheckV2(checkId, c) if err != nil { t.Fatal(err) } @@ -614,7 +769,17 @@ func TestLiveUpdateApiCheckV2(t *testing.T) { //Create your client with the token c := NewClient(token, realm) - err := UpdateApiCheckV2(1111, updateApiCheckV2Body, c) + checkId, err := CreateApiCheckV2(liveCreateApiV2Body, c) + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + if err := DeleteApiCheckV2(checkId, c); err != nil { + t.Errorf("failed to clean up api check %d: %v", checkId, err) + } + }) + + err = UpdateApiCheckV2(checkId, liveUpdateApiCheckV2Body, c) if err != nil { t.Fatal(err) } @@ -626,7 +791,12 @@ func TestLiveDeleteApiCheckV2(t *testing.T) { //Create your client with the token c := NewClient(token, realm) - err := DeleteApiCheckV2(1111, c) + checkId, err := CreateApiCheckV2(liveCreateApiV2Body, c) + if err != nil { + t.Fatal(err) + } + + err = DeleteApiCheckV2(checkId, c) if err != nil { t.Fatal(err) } @@ -635,13 +805,13 @@ func TestLiveDeleteApiCheckV2(t *testing.T) { func CreatePortCheckV2(test string, c *Client) (int, error) { - err := json.Unmarshal([]byte(test), &inputPortCheckV2Data) + err := json.Unmarshal([]byte(test), &liveInputPortCheckV2Data) if err != nil { return 0, err } // Make the request with your check settings and print result - res, reqDetail, err := c.CreatePortCheckV2(&inputPortCheckV2Data) + res, reqDetail, err := c.CreatePortCheckV2(&liveInputPortCheckV2Data) if err != nil { return 0, err } @@ -697,12 +867,17 @@ func TestLivePortCheckCreateUpdateAndDeleteV2(t *testing.T) { c := NewClient(token, realm) var err error - checkId, err := CreatePortCheckV2(createPortCheckV2Body, c) + checkId, err := CreatePortCheckV2(liveCreatePortCheckV2Body, c) if err != nil { t.Fatal(err) } + t.Cleanup(func() { + if err := DeletePortCheckV2(checkId, c); err != nil { + t.Errorf("failed to clean up port check %d: %v", checkId, err) + } + }) - err = UpdatePortCheckV2(checkId, updatePortCheckV2Body, c) + err = UpdatePortCheckV2(checkId, liveUpdatePortCheckV2Body, c) if err != nil { t.Fatal(err) } @@ -712,7 +887,7 @@ func TestLivePortCheckCreateUpdateAndDeleteV2(t *testing.T) { t.Fatal(err) } - err2 := UpdatePortCheckV2(checkId, createPortCheckV2Body, c) + err2 := UpdatePortCheckV2(checkId, liveCreatePortCheckV2Body, c) if err2 != nil { t.Fatal(err2) } @@ -722,23 +897,24 @@ func TestLivePortCheckCreateUpdateAndDeleteV2(t *testing.T) { t.Fatal(err) } - err = DeletePortCheckV2(checkId, c) - if err != nil { - t.Fatal(err) - } - } func TestLiveCreatePortCheckV2(t *testing.T) { //Create your client with the token c := NewClient(token, realm) - checkId, err := CreatePortCheckV2(createPortCheckV2Body, c) + checkId, err := CreatePortCheckV2(liveCreatePortCheckV2Body, c) if err != nil { t.Fatal(err) } log.Printf("Port Check ID: %d", checkId) + + t.Cleanup(func() { + if err := DeletePortCheckV2(checkId, c); err != nil { + t.Errorf("failed to clean up port check %d: %v", checkId, err) + } + }) } func TestLiveGetPortCheckV2(t *testing.T) { @@ -746,7 +922,17 @@ func TestLiveGetPortCheckV2(t *testing.T) { //Create your client with the token c := NewClient(token, realm) - err := GetPortCheckV2(1111, c) + checkId, err := CreatePortCheckV2(liveCreatePortCheckV2Body, c) + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + if err := DeletePortCheckV2(checkId, c); err != nil { + t.Errorf("failed to clean up port check %d: %v", checkId, err) + } + }) + + err = GetPortCheckV2(checkId, c) if err != nil { t.Fatal(err) } @@ -758,7 +944,17 @@ func TestLiveUpdatePortCheckV2(t *testing.T) { //Create your client with the token c := NewClient(token, realm) - err := UpdatePortCheckV2(1111, updatePortCheckV2Body, c) + checkId, err := CreatePortCheckV2(liveCreatePortCheckV2Body, c) + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + if err := DeletePortCheckV2(checkId, c); err != nil { + t.Errorf("failed to clean up port check %d: %v", checkId, err) + } + }) + + err = UpdatePortCheckV2(checkId, liveUpdatePortCheckV2Body, c) if err != nil { t.Fatal(err) } @@ -770,7 +966,12 @@ func TestLiveDeletePortCheckV2(t *testing.T) { //Create your client with the token c := NewClient(token, realm) - err := DeletePortCheckV2(1111, c) + checkId, err := CreatePortCheckV2(liveCreatePortCheckV2Body, c) + if err != nil { + t.Fatal(err) + } + + err = DeletePortCheckV2(checkId, c) if err != nil { t.Fatal(err) } @@ -779,7 +980,7 @@ func TestLiveDeletePortCheckV2(t *testing.T) { func TestLiveCreateLocationV2(t *testing.T) { - err := json.Unmarshal([]byte(createLocationV2Body), &inputLocationV2Data) + err := json.Unmarshal([]byte(liveCreateLocationV2Body), &liveInputLocationV2Data) if err != nil { t.Fatal(err) } @@ -787,20 +988,21 @@ func TestLiveCreateLocationV2(t *testing.T) { //Create your client with the token c := NewClient(token, realm) - fmt.Println(inputLocationV2Data) + fmt.Println(liveInputLocationV2Data) // Make the request with your location settings and print result - res, reqDetail, err := c.CreateLocationV2(&inputLocationV2Data) - if err != nil { - fmt.Println(err) - } else { - fmt.Println(reqDetail) - JsonPrint(res) - } - + res, reqDetail, err := c.CreateLocationV2(&liveInputLocationV2Data) if err != nil { t.Fatal(err) } + fmt.Println(reqDetail) + JsonPrint(res) + + t.Cleanup(func() { + if _, err := c.DeleteLocationV2(res.Location.ID); err != nil { + t.Errorf("failed to clean up location %q: %v", res.Location.ID, err) + } + }) } @@ -828,17 +1030,17 @@ func TestLiveDeleteLocationV2(t *testing.T) { //Create your client with the token c := NewClient(token, realm) - // Make the request with your location settings and print result - res, err := c.DeleteLocationV2("private-data-center-go-test") + created, _, err := c.CreateLocationV2(&LocationV2Input{Location: Location{ID: "private-data-center-go-test-delete", Label: "Data Center place", Default: false}}) if err != nil { - fmt.Println(err) - } else { - JsonPrint(res) + t.Fatal(err) } + // Make the request with your location settings and print result + res, err := c.DeleteLocationV2(created.Location.ID) if err != nil { t.Fatal(err) } + JsonPrint(res) } @@ -848,17 +1050,22 @@ func TestLiveDowntimeConfigurationCreateUpdateAndDeleteV2(t *testing.T) { c := NewClient(token, realm) var err error - checkId, err := CreateApiCheckV2(createApiV2Body, c) + checkId, err := CreateApiCheckV2(liveCreateApiV2Body, c) if err != nil { t.Fatal(err) } + t.Cleanup(func() { + if err := DeleteApiCheckV2(checkId, c); err != nil { + t.Errorf("failed to clean up api check %d: %v", checkId, err) + } + }) //There are restrictions on startTime and endTime for a downtime_configuration so we set the startTime to 10 days //in the future and the endTime to be 1 hour after the startTime tenDaysFromNow := time.Now().AddDate(0, 0, 10) year, month, day := tenDaysFromNow.Date() - startTime := fmt.Sprintf("%s-%s-%sT20:00:00.000Z", year, int(month), day) - endTime := fmt.Sprintf("%s-%s-%sT21:00:00.000Z", year, int(month), day) + startTime := fmt.Sprintf("%d-%02d-%02dT20:00:00.000Z", year, int(month), day) + endTime := fmt.Sprintf("%d-%02d-%02dT21:00:00.000Z", year, int(month), day) createDowntimeConfigurationV2Body := fmt.Sprintf("{\"downtimeConfiguration\":{\"name\":\"dc test\",\"description\":\"My super awesome test downtimeConfiguration\",\"rule\":\"augment_data\",\"testIds\":[%d],\"startTime\":\"%s\",\"endTime\":\"%s\"}}", checkId, startTime, endTime) @@ -866,6 +1073,11 @@ func TestLiveDowntimeConfigurationCreateUpdateAndDeleteV2(t *testing.T) { if err != nil { t.Fatal(err) } + t.Cleanup(func() { + if err := DeleteDowntimeConfigurationV2(downtimeConfigId, c); err != nil { + t.Errorf("failed to clean up downtime configuration %d: %v", downtimeConfigId, err) + } + }) err = GetDowntimeConfigurationV2(downtimeConfigId, c) if err != nil { @@ -884,27 +1096,17 @@ func TestLiveDowntimeConfigurationCreateUpdateAndDeleteV2(t *testing.T) { t.Fatal(err) } - err = DeleteDowntimeConfigurationV2(downtimeConfigId, c) - if err != nil { - t.Fatal(err) - } - - err = DeleteApiCheckV2(checkId, c) - if err != nil { - t.Fatal(err) - } - } func CreateDowntimeConfigurationV2(downtimeConfiguration string, c *Client) (int, error) { - err := json.Unmarshal([]byte(downtimeConfiguration), &inputDowntimeConfigurationV2Data) + err := json.Unmarshal([]byte(downtimeConfiguration), &liveInputDowntimeConfigurationV2Data) if err != nil { return 0, err } // Make the request with your check settings and print result - res, reqDetail, err := c.CreateDowntimeConfigurationV2(&inputDowntimeConfigurationV2Data) + res, reqDetail, err := c.CreateDowntimeConfigurationV2(&liveInputDowntimeConfigurationV2Data) if err != nil { return 0, err } @@ -953,3 +1155,771 @@ func DeleteDowntimeConfigurationV2(downtimeConfigId int, c *Client) error { return nil } + +func liveCreateSslCheckV2Input(name string) *SslCheckV2Input { + input := &SslCheckV2Input{} + input.Test.Name = name + input.Test.LocationIds = []string{"aws-us-east-1"} + input.Test.Frequency = 10 + input.Test.SchedulingStrategy = "round_robin" + input.Test.Active = true + input.Test.Automaticretries = 1 + input.Test.Host = "www.splunk.com" + input.Test.Port = 443 + input.Test.AllowSelfSigned = false + input.Test.AllowUntrustedRoot = false + return input +} + +func TestLiveSslCheckCreateUpdateAndDeleteV2(t *testing.T) { + //Create your client with the token + c := NewClient(token, realm) + + created, _, err := c.CreateSslCheckV2(liveCreateSslCheckV2Input("a-maximal-ssl-integration-test")) + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + if _, err := c.DeleteSslCheckV2(created.Test.ID); err != nil { + t.Errorf("failed to clean up ssl check %d: %v", created.Test.ID, err) + } + }) + JsonPrint(created) + + newFrequency := 30 + newActive := false + update := &SslCheckV2UpdateInput{} + update.Test.Frequency = &newFrequency + update.Test.Active = &newActive + + updated, _, err := c.UpdateSslCheckV2(created.Test.ID, update) + if err != nil { + t.Fatal(err) + } + JsonPrint(updated) + + res, _, err := c.GetSslCheckV2(created.Test.ID) + if err != nil { + t.Fatal(err) + } + JsonPrint(res) +} + +func TestLiveCreateSslCheckV2(t *testing.T) { + //Create your client with the token + c := NewClient(token, realm) + + res, _, err := c.CreateSslCheckV2(liveCreateSslCheckV2Input("a-minimal-ssl-integration-test")) + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + if _, err := c.DeleteSslCheckV2(res.Test.ID); err != nil { + t.Errorf("failed to clean up ssl check %d: %v", res.Test.ID, err) + } + }) + JsonPrint(res) +} + +func TestLiveGetSslCheckV2(t *testing.T) { + //Create your client with the token + c := NewClient(token, realm) + + created, _, err := c.CreateSslCheckV2(liveCreateSslCheckV2Input("a-gettable-ssl-integration-test")) + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + if _, err := c.DeleteSslCheckV2(created.Test.ID); err != nil { + t.Errorf("failed to clean up ssl check %d: %v", created.Test.ID, err) + } + }) + + res, _, err := c.GetSslCheckV2(created.Test.ID) + if err != nil { + t.Fatal(err) + } + JsonPrint(res) +} + +func TestLiveUpdateSslCheckV2(t *testing.T) { + //Create your client with the token + c := NewClient(token, realm) + + created, _, err := c.CreateSslCheckV2(liveCreateSslCheckV2Input("an-updatable-ssl-integration-test")) + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + if _, err := c.DeleteSslCheckV2(created.Test.ID); err != nil { + t.Errorf("failed to clean up ssl check %d: %v", created.Test.ID, err) + } + }) + + newFrequency := 30 + newActive := false + update := &SslCheckV2UpdateInput{} + update.Test.Frequency = &newFrequency + update.Test.Active = &newActive + + res, _, err := c.UpdateSslCheckV2(created.Test.ID, update) + if err != nil { + t.Fatal(err) + } + JsonPrint(res) + + confirmed, _, err := c.GetSslCheckV2(created.Test.ID) + if err != nil { + t.Fatal(err) + } + if confirmed.Test.Frequency != newFrequency { + t.Errorf("frequency = %d, want %d", confirmed.Test.Frequency, newFrequency) + } +} + +func TestLiveDeleteSslCheckV2(t *testing.T) { + //Create your client with the token + c := NewClient(token, realm) + + created, _, err := c.CreateSslCheckV2(liveCreateSslCheckV2Input("a-deletable-ssl-integration-test")) + if err != nil { + t.Fatal(err) + } + + if _, err := c.DeleteSslCheckV2(created.Test.ID); err != nil { + t.Fatal(err) + } +} + +func liveCreateTotpVariableV2Input(name string) *TotpVariableV2Input { + return &TotpVariableV2Input{Totp: TotpVariableInput{ + Name: name, + Secret: "JBSWY3DPEHPK3PXP", + Digits: 6, + Interval: 30, + HmacDigest: "sha1", + }} +} + +func TestLiveTotpVariableCreateUpdateAndDeleteV2(t *testing.T) { + //Create your client with the token + c := NewClient(token, realm) + + created, _, err := c.CreateTotpVariableV2(liveCreateTotpVariableV2Input("a-maximal-totp-integration-test")) + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + if _, err := c.DeleteTotpVariableV2(created.Totp.ID); err != nil { + t.Errorf("failed to clean up totp variable %d: %v", created.Totp.ID, err) + } + }) + JsonPrint(created) + + newDigits := 8 + newInterval := 60 + updated, _, err := c.UpdateTotpVariableV2(created.Totp.ID, &TotpVariableV2UpdateInput{Totp: TotpVariableUpdateInput{ + Digits: &newDigits, + Interval: &newInterval, + }}) + if err != nil { + t.Fatal(err) + } + JsonPrint(updated) + + res, _, err := c.GetTotpVariableV2(created.Totp.ID) + if err != nil { + t.Fatal(err) + } + JsonPrint(res) + + list, _, err := c.GetTotpVariablesV2() + if err != nil { + t.Fatal(err) + } + found := false + for _, totp := range list.Totps { + if totp.ID == created.Totp.ID { + found = true + break + } + } + if !found { + t.Errorf("created totp variable %d not present in GetTotpVariablesV2 response", created.Totp.ID) + } +} + +func TestLiveCreateTotpVariableV2(t *testing.T) { + //Create your client with the token + c := NewClient(token, realm) + + res, _, err := c.CreateTotpVariableV2(liveCreateTotpVariableV2Input("a-minimal-totp-integration-test")) + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + if _, err := c.DeleteTotpVariableV2(res.Totp.ID); err != nil { + t.Errorf("failed to clean up totp variable %d: %v", res.Totp.ID, err) + } + }) + JsonPrint(res) +} + +func TestLiveGetTotpVariableV2(t *testing.T) { + //Create your client with the token + c := NewClient(token, realm) + + created, _, err := c.CreateTotpVariableV2(liveCreateTotpVariableV2Input("a-gettable-totp-integration-test")) + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + if _, err := c.DeleteTotpVariableV2(created.Totp.ID); err != nil { + t.Errorf("failed to clean up totp variable %d: %v", created.Totp.ID, err) + } + }) + + res, _, err := c.GetTotpVariableV2(created.Totp.ID) + if err != nil { + t.Fatal(err) + } + JsonPrint(res) +} + +func TestLiveUpdateTotpVariableV2(t *testing.T) { + //Create your client with the token + c := NewClient(token, realm) + + created, _, err := c.CreateTotpVariableV2(liveCreateTotpVariableV2Input("an-updatable-totp-integration-test")) + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + if _, err := c.DeleteTotpVariableV2(created.Totp.ID); err != nil { + t.Errorf("failed to clean up totp variable %d: %v", created.Totp.ID, err) + } + }) + + newDigits := 8 + newInterval := 60 + res, _, err := c.UpdateTotpVariableV2(created.Totp.ID, &TotpVariableV2UpdateInput{Totp: TotpVariableUpdateInput{ + Digits: &newDigits, + Interval: &newInterval, + }}) + if err != nil { + t.Fatal(err) + } + JsonPrint(res) + + confirmed, _, err := c.GetTotpVariableV2(created.Totp.ID) + if err != nil { + t.Fatal(err) + } + if confirmed.Totp.Digits != newDigits { + t.Errorf("digits = %d, want %d", confirmed.Totp.Digits, newDigits) + } + if confirmed.Totp.Interval != newInterval { + t.Errorf("interval = %d, want %d", confirmed.Totp.Interval, newInterval) + } +} + +func TestLiveDeleteTotpVariableV2(t *testing.T) { + //Create your client with the token + c := NewClient(token, realm) + + created, _, err := c.CreateTotpVariableV2(liveCreateTotpVariableV2Input("a-deletable-totp-integration-test")) + if err != nil { + t.Fatal(err) + } + + if _, err := c.DeleteTotpVariableV2(created.Totp.ID); err != nil { + t.Fatal(err) + } +} + +const liveTestCaCertContent = "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURJekNDQWd1Z0F3SUJBZ0lVZWtuOHdoNzhCRG9CaHkxZ21xWXBPVjhnWGJjd0RRWUpLb1pJaHZjTkFRRUwKQlFBd0lURWZNQjBHQTFVRUF3d1djM2x1ZEdndGFXNTBaV2R5WVhScGIyNHRkR1Z6ZERBZUZ3MHlOakE0TVRBeApOVFU0TXpsYUZ3MHpOakE0TURjeE5UVTRNemxhTUNFeEh6QWRCZ05WQkFNTUZuTjViblJvTFdsdWRHVm5jbUYwCmFXOXVMWFJsYzNRd2dnRWlNQTBHQ1NxR1NJYjNEUUVCQVFVQUE0SUJEd0F3Z2dFS0FvSUJBUURKODdKRWVNKzUKV0x5UGF1ZnBmSzVJbWZJMFJhSndybnozZnEzd3c3d2VnSlR4bkt2WkJZNUJaOHhyVjVWcDl6WS8vUE5TM29GZwpLbUJUQnVQZEVMVHU0VkZKZDlTc0lYajhtUnNoNS81aWJUekxaNk1GNVI3NjIzZkJpQ1lNMWZOVXhRMVQvZTlIClV1cC9xb2ZBZ21nMDJEZzJUWE1RMU1IVi9HUGJvaTBjWVR0TnlZR1hVbWNDYkhCZllkNGN1UmI4a0FWbE1UZW8KTjlmU2FEakxqbzNnazhIK1VITlpaRU1WS0dVczkzUE1MV2JzV2NmTE01TWptU21FakxrT3BMbXkwRXRYWC9MOApGYVordU9YTmtLRU5XcCtlRVJJb256aVlPcXN3bitCdVAxbmpMUktTV3AvK1ZtL0s2V1ZGTDFPOHFlOEN6Y3J2CmpWWXNraFVHR21pbkFnTUJBQUdqVXpCUk1CMEdBMVVkRGdRV0JCVDMrMDhpTkdkajFLTEdrdlY5TVE5bXkvd0gKWURBZkJnTlZIU01FR0RBV2dCVDMrMDhpTkdkajFLTEdrdlY5TVE5bXkvd0hZREFQQmdOVkhSTUJBZjhFQlRBRApBUUgvTUEwR0NTcUdTSWIzRFFFQkN3VUFBNElCQVFCOGxLK0hVays0Wk5ZUG5ZWE94QzhBK1VTL2tvU0U5cmlECjlYb29Oc3diQ3pMR2RWTkFKb1pybHAzRnZBZVZXTXN1bkNrdE9YaTlUSEJnN0c0cmJ3b09PaE1nTU11Q3Z4QlQKOHByZmNOYm5xU3lSemNGWTIyNHBuYU85ci9YajNFTkhpZmg0QzBKZ2xVTk5wWjgwdTFUS0ZKaGl3OUlicUFCdQpXM0pEeEY2MGk3R2hmdnFmUmZBWkt3cFNOYTFBaTVvTFBYSVVlaytFeEFaeC8wd3htT0xOdGxhK2RyT0UvWUpYCjNsZGJ5cjBydFZkZEVTcy9tR1k3Y0tMQmhLVkRWTUpwTHkzSi9qTEIxUm5uOHZtRmF6b1JyTjYvUXhNajVGTSsKdDhINDRnaVRwbC90VTdDelpETTdNbHByN01qMU80WFdqSWlRcW5HVXhPNElxL1d0dWZIQwotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==" + +func liveCreateCaCertificateV2Input(name string) *CaCertificateV2Input { + return &CaCertificateV2Input{CaCert: CaCertificateInput{ + Name: name, + Content: liveTestCaCertContent, + FileExtension: "pem", + Filename: "ca_cert_file.pem", + }} +} + +func TestLiveCaCertificateCreateUpdateAndDeleteV2(t *testing.T) { + //Create your client with the token + c := NewClient(token, realm) + + created, _, err := c.CreateCaCertificateV2(liveCreateCaCertificateV2Input("a-maximal-cacert-integration-test")) + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + if _, err := c.DeleteCaCertificateV2(created.CaCert.ID); err != nil { + t.Errorf("failed to clean up ca certificate %d: %v", created.CaCert.ID, err) + } + }) + JsonPrint(created) + + newDescription := "updated by integration test" + updated, _, err := c.UpdateCaCertificateV2(created.CaCert.ID, &CaCertificateV2UpdateInput{CaCert: CaCertificateUpdateInput{ + Description: &newDescription, + }}) + if err != nil { + t.Fatal(err) + } + JsonPrint(updated) + + res, _, err := c.GetCaCertificateV2(created.CaCert.ID) + if err != nil { + t.Fatal(err) + } + JsonPrint(res) + + list, _, err := c.GetCaCertificatesV2() + if err != nil { + t.Fatal(err) + } + found := false + for _, cert := range list.CaCerts { + if cert.ID == created.CaCert.ID { + found = true + break + } + } + if !found { + t.Errorf("created ca certificate %d not present in GetCaCertificatesV2 response", created.CaCert.ID) + } +} + +func TestLiveCreateCaCertificateV2(t *testing.T) { + //Create your client with the token + c := NewClient(token, realm) + + res, _, err := c.CreateCaCertificateV2(liveCreateCaCertificateV2Input("a-minimal-cacert-integration-test")) + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + if _, err := c.DeleteCaCertificateV2(res.CaCert.ID); err != nil { + t.Errorf("failed to clean up ca certificate %d: %v", res.CaCert.ID, err) + } + }) + JsonPrint(res) +} + +func TestLiveGetCaCertificateV2(t *testing.T) { + //Create your client with the token + c := NewClient(token, realm) + + created, _, err := c.CreateCaCertificateV2(liveCreateCaCertificateV2Input("a-gettable-cacert-integration-test")) + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + if _, err := c.DeleteCaCertificateV2(created.CaCert.ID); err != nil { + t.Errorf("failed to clean up ca certificate %d: %v", created.CaCert.ID, err) + } + }) + + res, _, err := c.GetCaCertificateV2(created.CaCert.ID) + if err != nil { + t.Fatal(err) + } + JsonPrint(res) +} + +func TestLiveUpdateCaCertificateV2(t *testing.T) { + //Create your client with the token + c := NewClient(token, realm) + + created, _, err := c.CreateCaCertificateV2(liveCreateCaCertificateV2Input("an-updatable-cacert-integration-test")) + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + if _, err := c.DeleteCaCertificateV2(created.CaCert.ID); err != nil { + t.Errorf("failed to clean up ca certificate %d: %v", created.CaCert.ID, err) + } + }) + + newDescription := "updated by integration test" + res, _, err := c.UpdateCaCertificateV2(created.CaCert.ID, &CaCertificateV2UpdateInput{CaCert: CaCertificateUpdateInput{ + Description: &newDescription, + }}) + if err != nil { + t.Fatal(err) + } + JsonPrint(res) + + confirmed, _, err := c.GetCaCertificateV2(created.CaCert.ID) + if err != nil { + t.Fatal(err) + } + if confirmed.CaCert.Description != newDescription { + t.Errorf("description = %q, want %q", confirmed.CaCert.Description, newDescription) + } +} + +func TestLiveDeleteCaCertificateV2(t *testing.T) { + //Create your client with the token + c := NewClient(token, realm) + + created, _, err := c.CreateCaCertificateV2(liveCreateCaCertificateV2Input("a-deletable-cacert-integration-test")) + if err != nil { + t.Fatal(err) + } + + if _, err := c.DeleteCaCertificateV2(created.CaCert.ID); err != nil { + t.Fatal(err) + } +} + +const liveTestClientCertPrivateKeyContent = "LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2QUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktZd2dnU2lBZ0VBQW9JQkFRREo4N0pFZU0rNVdMeVAKYXVmcGZLNUltZkkwUmFKd3JuejNmcTN3dzd3ZWdKVHhuS3ZaQlk1Qlo4eHJWNVZwOXpZLy9QTlMzb0ZnS21CVApCdVBkRUxUdTRWRkpkOVNzSVhqOG1Sc2g1LzVpYlR6TFo2TUY1Ujc2MjNmQmlDWU0xZk5VeFExVC9lOUhVdXAvCnFvZkFnbWcwMkRnMlRYTVExTUhWL0dQYm9pMGNZVHROeVlHWFVtY0NiSEJmWWQ0Y3VSYjhrQVZsTVRlb045ZlMKYURqTGpvM2drOEgrVUhOWlpFTVZLR1VzOTNQTUxXYnNXY2ZMTTVNam1TbUVqTGtPcExteTBFdFhYL0w4RmFaKwp1T1hOa0tFTldwK2VFUklvbnppWU9xc3duK0J1UDFuakxSS1NXcC8rVm0vSzZXVkZMMU84cWU4Q3pjcnZqVllzCmtoVUdHbWluQWdNQkFBRUNnZ0VBSXI0SVpJd3VIREkrV2lQbm1yem0xTG1iTjgvazlxS21BQVBzazVkd3hRU1UKMnczN2FGM3l6NkMyUTU4eEpxWXZVSW5KS0cvNzdObk5jV3NsaHpIcEZwRnZwUVoyOFZmZTB3SFo3NWJVSmdXcAo2RW8vZXZPa1JUNjlWdTkvc0VTY1ZIQ0Q3dmVvRXVxYVNmVkIzbVh3M0dwNEhTdHN5Sy81V3NGTlFvc2ZYSnExClZtdGFqMWxkRXR2UmhLanY5ZkdYcnRZSFpVR2c2VzVqV0h0a0F5VmZhSkdSbU9HcTA2UVd4aWg0YW9lVHVhQ3QKSUxwci9lUkdQbHpiSVIxWXlhblhGMzVkdGVRTE9mQWR2YXBCWFJlbzA0VWZSWW1hbnVVYSt3Um5ENnBUTEhIKwpNaFNoQkZhQzR4OERxOGJJZnFWOGR5VElyU3pnUUtQY0t3akM3dGZqQVFLQmdRRG1KQklhYnh0L0pacXEvc2gzCm9MUmg5aERFcEV1ejVtYnAyaVNhcXZEYkZQRm9Ic2JVQ2daTHZUL01seS9QYmY0QlBYdjluRWkyRGphNTFFVHAKeWNMZDNnU2JkVS9pQzgwV1BOQzErZ0hwbDBYS0RTaEtGRlRHT2orZ1VNc2ExMkl6aUFRK0R1Ymh5Y1c2Y0QrbwpOeFc2YUloT0JjZHBjK0t0ZUtWWmxVQXRBUUtCZ1FEZ3BNZDN6cktINXNvemlWaXBtRlRwNEI1em1Pd0JPc3grCjN5c2tBSFNXZUkyVkl5T1djcGoxWlhzMUtwaUlUMU9FVGU3UWVpTUFXcmNoVjRMekxiVXdwRzYvRG1icUU5aEUKTW12bmYrS01wL3F1ZVU4akdNSS9PdXpRdVBNVHFIcml6Y0xkdTZCYStnVVNFV1EzdENaeXM5MnlQdlpYTDN3dgpGMkVMZ3ZRTnB3S0JnSHdPOWJOS01ZaFl2UWR3VUtBc0FSRE5sRHhzVkdLbDBOUSt3M3ljcVRsd0VMSVA1UjVvClNQeUxCOWxCcG9RcXhzSGtZdkpUVE43V3lxbGh3OFJDL3NpYTVlRG5YQ2grTkEvSXVMbGdDNmZmNDc4SFdMQ1cKUlJ5V1NiWWgxMXFnd0U4SEEwSnd4Z1R3djZYQTNJL1JJZVZhZEIrYS9lUGFsRmJ1c2pPWVFRQUJBb0dBZThoUQpZU1AwSEE1L3ZJWWg1TkdiZUlPV1Evd3ZqejNuRU1ISDg3Nk1mNTFONXEvR0hGQnBHRThpNU5qajA3aGlQTFQwCnNzdWFIY2Zld1BDSHA1ZTREMldMNEpyKytseVUvbjhLRmpYUmo4Ky93Z1AySjFDdE9Fb3YwNU1WM2U4b1IzRTUKdnhSejk2MXN2ZGYzY1BwRGRWREhDRURKWEtFOXZIVVZkRkprU0dFQ2dZQmM5ZUNrR2Z0clk3SGk3WktadnFEVwpVd210S2t5aW9zTnFraW4zZUZNRE50UGVMaGh5S3laa1N3WExpcVYyVkZqNG1uYXd4KzFYd2hoanlTTFJualhECmlKdlRQQTFtKzBNRmQrWWhiRjZkVmxQaThTNkMzeDNremlFOGdSZVduVDBNaGx2UG8rVm10Q1E1empRMTFzZUMKWFVoQytiaEdxZjFaRUpua1RsbHUrUT09Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K" + +func liveCreateClientCertificateV2Input(name string) *ClientCertificateV2Input { + return &ClientCertificateV2Input{Certificate: ClientCertificateInput{ + Name: name, + Domain: "api.integration-test.example.com", + PublicKey: ClientCertificateKeyInput{ + Content: liveTestCaCertContent, + Filename: "client.crt", + FileExtension: "pem", + }, + PrivateKey: ClientCertificatePrivateKeyInput{ + Content: liveTestClientCertPrivateKeyContent, + Filename: "client.key", + FileExtension: "pem", + }, + }} +} + +func TestLiveClientCertificateCreateUpdateAndDeleteV2(t *testing.T) { + //Create your client with the token + c := NewClient(token, realm) + + created, _, err := c.CreateClientCertificateV2(liveCreateClientCertificateV2Input("a-maximal-clientcert-integration-test")) + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + if _, err := c.DeleteClientCertificateV2(created.Certificate.ID); err != nil { + t.Errorf("failed to clean up client certificate %d: %v", created.Certificate.ID, err) + } + }) + JsonPrint(created) + + newDescription := "updated by integration test" + updated, _, err := c.UpdateClientCertificateV2(created.Certificate.ID, &ClientCertificateV2UpdateInput{Certificate: ClientCertificateUpdateInput{ + Description: &newDescription, + }}) + if err != nil { + t.Fatal(err) + } + JsonPrint(updated) + + res, _, err := c.GetClientCertificateV2(created.Certificate.ID) + if err != nil { + t.Fatal(err) + } + JsonPrint(res) + + list, _, err := c.GetClientCertificatesV2() + if err != nil { + t.Fatal(err) + } + found := false + for _, cert := range list.Certificates { + if cert.ID == created.Certificate.ID { + found = true + break + } + } + if !found { + t.Errorf("created client certificate %d not present in GetClientCertificatesV2 response", created.Certificate.ID) + } +} + +func TestLiveCreateClientCertificateV2(t *testing.T) { + //Create your client with the token + c := NewClient(token, realm) + + res, _, err := c.CreateClientCertificateV2(liveCreateClientCertificateV2Input("a-minimal-clientcert-integration-test")) + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + if _, err := c.DeleteClientCertificateV2(res.Certificate.ID); err != nil { + t.Errorf("failed to clean up client certificate %d: %v", res.Certificate.ID, err) + } + }) + JsonPrint(res) +} + +func TestLiveGetClientCertificateV2(t *testing.T) { + //Create your client with the token + c := NewClient(token, realm) + + created, _, err := c.CreateClientCertificateV2(liveCreateClientCertificateV2Input("a-gettable-clientcert-integration-test")) + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + if _, err := c.DeleteClientCertificateV2(created.Certificate.ID); err != nil { + t.Errorf("failed to clean up client certificate %d: %v", created.Certificate.ID, err) + } + }) + + res, _, err := c.GetClientCertificateV2(created.Certificate.ID) + if err != nil { + t.Fatal(err) + } + JsonPrint(res) +} + +func TestLiveUpdateClientCertificateV2(t *testing.T) { + //Create your client with the token + c := NewClient(token, realm) + + created, _, err := c.CreateClientCertificateV2(liveCreateClientCertificateV2Input("an-updatable-clientcert-integration-test")) + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + if _, err := c.DeleteClientCertificateV2(created.Certificate.ID); err != nil { + t.Errorf("failed to clean up client certificate %d: %v", created.Certificate.ID, err) + } + }) + + newDescription := "updated by integration test" + res, _, err := c.UpdateClientCertificateV2(created.Certificate.ID, &ClientCertificateV2UpdateInput{Certificate: ClientCertificateUpdateInput{ + Description: &newDescription, + }}) + if err != nil { + t.Fatal(err) + } + JsonPrint(res) + + confirmed, _, err := c.GetClientCertificateV2(created.Certificate.ID) + if err != nil { + t.Fatal(err) + } + if confirmed.Certificate.Description != newDescription { + t.Errorf("description = %q, want %q", confirmed.Certificate.Description, newDescription) + } +} + +func TestLiveDeleteClientCertificateV2(t *testing.T) { + //Create your client with the token + c := NewClient(token, realm) + + created, _, err := c.CreateClientCertificateV2(liveCreateClientCertificateV2Input("a-deletable-clientcert-integration-test")) + if err != nil { + t.Fatal(err) + } + + if _, err := c.DeleteClientCertificateV2(created.Certificate.ID); err != nil { + t.Fatal(err) + } +} + +func TestLiveGetVariablesV2(t *testing.T) { + //Create your client with the token + c := NewClient(token, realm) + + created, _, err := c.CreateVariableV2(&VariableV2Input{Variable: Variable{Name: "a-listable-variable-integration-test", Value: "bar", Secret: false}}) + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + if _, err := c.DeleteVariableV2(created.Variable.ID); err != nil { + t.Errorf("failed to clean up variable %d: %v", created.Variable.ID, err) + } + }) + + res, _, err := c.GetVariablesV2() + if err != nil { + t.Fatal(err) + } + + found := false + for _, v := range res.Variable { + if v.ID == created.Variable.ID { + found = true + break + } + } + if !found { + t.Errorf("created variable %d not present in GetVariablesV2 response", created.Variable.ID) + } +} + +func TestLiveGetLocationsV2(t *testing.T) { + //Create your client with the token + c := NewClient(token, realm) + + res, _, err := c.GetLocationsV2() + if err != nil { + t.Fatal(err) + } + if len(res.Location) == 0 { + t.Error("expected at least one location in GetLocationsV2 response, got none") + } + JsonPrint(res) +} + +func TestLiveGetDowntimeConfigurationsV2(t *testing.T) { + //Create your client with the token + c := NewClient(token, realm) + + checkId, err := CreateApiCheckV2(createMinimalApiV2Body, c) + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + if err := DeleteApiCheckV2(checkId, c); err != nil { + t.Errorf("failed to clean up api check %d: %v", checkId, err) + } + }) + + tenDaysFromNow := time.Now().AddDate(0, 0, 10) + year, month, day := tenDaysFromNow.Date() + startTime := fmt.Sprintf("%d-%02d-%02dT20:00:00.000Z", year, int(month), day) + endTime := fmt.Sprintf("%d-%02d-%02dT21:00:00.000Z", year, int(month), day) + + createDowntimeConfigurationV2Body := fmt.Sprintf("{\"downtimeConfiguration\":{\"name\":\"dc list test\",\"description\":\"created by integration test\",\"rule\":\"augment_data\",\"testIds\":[%d],\"startTime\":\"%s\",\"endTime\":\"%s\"}}", checkId, startTime, endTime) + + downtimeConfigId, err := CreateDowntimeConfigurationV2(createDowntimeConfigurationV2Body, c) + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + if err := DeleteDowntimeConfigurationV2(downtimeConfigId, c); err != nil { + t.Errorf("failed to clean up downtime configuration %d: %v", downtimeConfigId, err) + } + }) + + res, _, err := c.GetDowntimeConfigurationsV2(&GetDowntimeConfigurationsV2Options{}) + if err != nil { + t.Fatal(err) + } + + found := false + for _, dc := range res.Downtimeconfigurations { + if dc.ID == downtimeConfigId { + found = true + break + } + } + if !found { + t.Errorf("created downtime configuration %d not present in GetDowntimeConfigurationsV2 response", downtimeConfigId) + } +} + +func TestLiveGetExcludedFileTypesV2(t *testing.T) { + //Create your client with the token + c := NewClient(token, realm) + + res, _, err := c.GetExcludedFileTypesV2() + if err != nil { + t.Fatal(err) + } + JsonPrint(res) +} + +func liveCreateHttpCheckV2WithNullablePortInput(name string) *HttpCheckV2InputWithNullablePort { + input := &HttpCheckV2InputWithNullablePort{} + input.Test.Name = name + input.Test.Type = "http" + input.Test.URL = "https://www.splunk.com" + input.Test.LocationIds = []string{"aws-us-east-1"} + input.Test.Frequency = 10 + input.Test.SchedulingStrategy = "round_robin" + input.Test.Active = true + input.Test.RequestMethod = "GET" + input.Test.Automaticretries = 1 + input.Test.Port = *NewNullableInt(443) + return input +} + +func TestLiveHttpCheckWithNullablePortCreateUpdateAndDeleteV2(t *testing.T) { + //Create your client with the token + c := NewClient(token, realm) + + created, _, err := c.CreateHttpCheckV2WithNullablePort(liveCreateHttpCheckV2WithNullablePortInput("a-maximal-nullable-port-http-integration-test")) + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + if _, err := c.DeleteHttpCheckV2(created.Test.ID); err != nil { + t.Errorf("failed to clean up http check %d: %v", created.Test.ID, err) + } + }) + JsonPrint(created) + + update := liveCreateHttpCheckV2WithNullablePortInput("a-maximal-nullable-port-http-integration-test") + update.Test.Port = *NewNullInt() + + updated, _, err := c.UpdateHttpCheckV2WithNullablePort(created.Test.ID, update) + if err != nil { + t.Fatal(err) + } + JsonPrint(updated) + + res, _, err := c.GetHttpCheckV2WithNullablePort(created.Test.ID) + if err != nil { + t.Fatal(err) + } + if res.Test.Port.Value != nil { + t.Errorf("port = %v, want nil", *res.Test.Port.Value) + } + JsonPrint(res) +} + +func TestLiveGetHttpCheckV2ReturnsErrorForNonexistentID(t *testing.T) { + //Create your client with the token + c := NewClient(token, realm) + + if _, _, err := c.GetHttpCheckV2(0); err == nil { + t.Fatal("expected error getting nonexistent http check, got nil") + } +} + +func TestLiveGetVariableV2ReturnsErrorForNonexistentID(t *testing.T) { + //Create your client with the token + c := NewClient(token, realm) + + if _, _, err := c.GetVariableV2(0); err == nil { + t.Fatal("expected error getting nonexistent variable, got nil") + } +} + +func TestLiveDeleteApiCheckV2ReturnsErrorForNonexistentID(t *testing.T) { + //Create your client with the token + c := NewClient(token, realm) + + if _, err := c.DeleteApiCheckV2(0); err == nil { + t.Fatal("expected error deleting nonexistent api check, got nil") + } +} + +func TestLiveCreateVariableV2ReturnsErrorOnDuplicateName(t *testing.T) { + //Create your client with the token + c := NewClient(token, realm) + + created, _, err := c.CreateVariableV2(&VariableV2Input{Variable: Variable{Name: "a-duplicate-name-integration-test", Value: "bar", Secret: false}}) + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + if _, err := c.DeleteVariableV2(created.Variable.ID); err != nil { + t.Errorf("failed to clean up variable %d: %v", created.Variable.ID, err) + } + }) + + if _, _, err := c.CreateVariableV2(&VariableV2Input{Variable: Variable{Name: "a-duplicate-name-integration-test", Value: "bar", Secret: false}}); err == nil { + t.Fatal("expected error creating variable with duplicate name, got nil") + } +} From 01075bb655948728c26c60418408db77ec84c77d Mon Sep 17 00:00:00 2001 From: Jaideep Padhye Date: Mon, 10 Aug 2026 11:02:24 -0700 Subject: [PATCH 09/13] Explicitly skip syntheticsclient (v1) tests instead of excluding the package v1 is slated for deprecation, but it was silently absent from `go test` output rather than reported at all -- FILES scoped `test`/`test-cover` to syntheticsclientv2 only, so the v1 package was invisible in CI, not skipped. Add skipDeprecated(t), called first in each of the 9 v1 tests, so `go test ./syntheticsclient/...` now reports every one of them as an explicit SKIP with a deprecation note. Makefile: run both packages in test/test-cover (TEST_FILES) so the SKIPs show up in the test-reporter summary, but keep -coverpkg scoped to syntheticsclientv2 only so v1 still doesn't count toward the coverage gate. build stays v2-only; v1 isn't being changed, just deprecated. Co-Authored-By: Claude --- Makefile | 12 ++++++++---- syntheticsclient/create_browsercheck_test.go | 1 + syntheticsclient/create_httpcheck_test.go | 1 + syntheticsclient/delete_browsercheck_test.go | 1 + syntheticsclient/delete_httpcheck_test.go | 1 + syntheticsclient/get_check_test.go | 1 + syntheticsclient/synthetics_test.go | 11 +++++++++++ syntheticsclient/update_browsercheck_test.go | 1 + syntheticsclient/update_httpcheck_test.go | 1 + 9 files changed, 26 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 934dded..9c4ab24 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,12 @@ .PHONY: default all build clean test fmtcheck test-cover test-integration -# syntheticsclient (v1) is deprecated and excluded from tests, builds, and -# coverage; only syntheticsclientv2 is exercised here. +# syntheticsclient (v1) is deprecated and excluded from builds and coverage; +# only syntheticsclientv2 is built/covered. v1's tests still run via +# TEST_FILES below so `go test` reports them as an explicit SKIP (see +# skipDeprecated in syntheticsclient/synthetics_test.go) rather than the +# package silently vanishing from CI output. FILES=./syntheticsclientv2/... +TEST_FILES=./syntheticsclient/... ./syntheticsclientv2/... default: test @@ -29,11 +33,11 @@ fmtcheck: fmt lint test: fmtcheck @echo "==> Running all tests" - go test $(FILES) -v -timeout=30s -parallel=4 -cover + go test $(TEST_FILES) -v -timeout=30s -parallel=4 -cover -coverpkg=$(FILES) test-cover: clean fmtcheck @echo "==> Running all tests with coverage and JSON output" - go test $(FILES) -timeout=30s -parallel=8 -json -cover -covermode=atomic -coverprofile coverage.txt > test-results.json + go test $(TEST_FILES) -timeout=30s -parallel=8 -json -cover -covermode=atomic -coverpkg=$(FILES) -coverprofile coverage.txt > test-results.json # Runs the live integration suite (syntheticsclientv2/integration_test.go) against a real # Synthetics org. Requires API_ACCESS_TOKEN and REALM in the environment. Not part of the diff --git a/syntheticsclient/create_browsercheck_test.go b/syntheticsclient/create_browsercheck_test.go index 1a56703..c653b61 100644 --- a/syntheticsclient/create_browsercheck_test.go +++ b/syntheticsclient/create_browsercheck_test.go @@ -26,6 +26,7 @@ var ( ) func TestCreateBrowseCheck(t *testing.T) { + skipDeprecated(t) setup() defer teardown() diff --git a/syntheticsclient/create_httpcheck_test.go b/syntheticsclient/create_httpcheck_test.go index 66c0927..f5bba57 100644 --- a/syntheticsclient/create_httpcheck_test.go +++ b/syntheticsclient/create_httpcheck_test.go @@ -26,6 +26,7 @@ var ( ) func TestCreateHttpCheck(t *testing.T) { + skipDeprecated(t) setup() defer teardown() diff --git a/syntheticsclient/delete_browsercheck_test.go b/syntheticsclient/delete_browsercheck_test.go index 3dcefb0..48105a6 100644 --- a/syntheticsclient/delete_browsercheck_test.go +++ b/syntheticsclient/delete_browsercheck_test.go @@ -24,6 +24,7 @@ var ( ) func TestDeleteBrowseCheck(t *testing.T) { + skipDeprecated(t) setup() defer teardown() diff --git a/syntheticsclient/delete_httpcheck_test.go b/syntheticsclient/delete_httpcheck_test.go index 2a5489c..022d2d5 100644 --- a/syntheticsclient/delete_httpcheck_test.go +++ b/syntheticsclient/delete_httpcheck_test.go @@ -24,6 +24,7 @@ var ( ) func TestDeleteHttpCheck(t *testing.T) { + skipDeprecated(t) setup() defer teardown() diff --git a/syntheticsclient/get_check_test.go b/syntheticsclient/get_check_test.go index ddd118d..466bc2d 100644 --- a/syntheticsclient/get_check_test.go +++ b/syntheticsclient/get_check_test.go @@ -27,6 +27,7 @@ var ( ) func TestGetBrowserCheck(t *testing.T) { + skipDeprecated(t) setup() defer teardown() diff --git a/syntheticsclient/synthetics_test.go b/syntheticsclient/synthetics_test.go index e11627b..dc18df9 100644 --- a/syntheticsclient/synthetics_test.go +++ b/syntheticsclient/synthetics_test.go @@ -53,7 +53,17 @@ func testMethod(t *testing.T, r *http.Request, want string) { } } +// skipDeprecated marks a test as skipped rather than removing it outright, +// so `go test ./...` still reports every syntheticsclient (v1) test by name +// with a SKIP status and this note, instead of the package silently +// vanishing from CI output. See SYN-6889: v1 is slated for deprecation and +// is excluded from the coverage gate and build (Makefile FILES var). +func skipDeprecated(t *testing.T) { + t.Skip("syntheticsclient (v1) is deprecated; skipping in favor of syntheticsclientv2") +} + func TestConfigurableClient(t *testing.T) { + skipDeprecated(t) testMux = http.NewServeMux() testServer = httptest.NewServer(testMux) args := ClientArgs{ @@ -72,6 +82,7 @@ func TestConfigurableClient(t *testing.T) { } func TestConfigurableClientTimeout(t *testing.T) { + skipDeprecated(t) testMux = http.NewServeMux() testServer = httptest.NewServer(testMux) diff --git a/syntheticsclient/update_browsercheck_test.go b/syntheticsclient/update_browsercheck_test.go index df57624..5d67895 100644 --- a/syntheticsclient/update_browsercheck_test.go +++ b/syntheticsclient/update_browsercheck_test.go @@ -51,6 +51,7 @@ var ( ) func TestUpdateBrowserCheck(t *testing.T) { + skipDeprecated(t) setup() defer teardown() diff --git a/syntheticsclient/update_httpcheck_test.go b/syntheticsclient/update_httpcheck_test.go index 1219d24..b56ecca 100644 --- a/syntheticsclient/update_httpcheck_test.go +++ b/syntheticsclient/update_httpcheck_test.go @@ -26,6 +26,7 @@ var ( ) func TestUpdateHttpCheck(t *testing.T) { + skipDeprecated(t) setup() defer teardown() From 04d7dfd007b35b7325ce033ccf210c9baab2555a Mon Sep 17 00:00:00 2001 From: Jaideep Padhye Date: Mon, 10 Aug 2026 14:43:33 -0700 Subject: [PATCH 10/13] Fix fork-PR write failures and cross-PR concurrency in new CI workflows Addresses two review comments on PR #53: - ci.yml: GitHub downgrades GITHUB_TOKEN to read-only for pull_request runs from forks regardless of the checks:write/pull-requests:write requested at the workflow level, so the test-reporter check-run and the coverage PR-comment step would fail on any fork PR with "Resource not accessible by integration". Skip both steps when the PR's head repo isn't this repo; the job itself still fails on a real test/coverage regression either way. - integration-test.yml: the concurrency group was keyed by head repo + branch, so two different approved PRs could run the integration-test job at the same time even though they hit the same live org (sg0) and create the same fixed-name resources (beep-var, terraform-test-foo-301, and fixtures like the one TestLiveCreateVariableV2ReturnsErrorOnDuplicateName exercises). Use one fixed group for all PRs instead. Moved the concurrency key from the workflow level to just the integration-test job, so a second PR's resolve job (artifact validation, opening the queued check) still runs immediately rather than queuing behind however long the first PR's environment approval takes. Co-Authored-By: Claude --- .github/workflows/ci.yml | 12 +++++++++--- .github/workflows/integration-test.yml | 21 +++++++++++++-------- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 14e773f..5b4eb57 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,9 +36,14 @@ jobs: version: latest - run: make lint - run: make test-cover + # GitHub downgrades GITHUB_TOKEN to read-only for pull_request runs from forks, + # regardless of the checks:write/pull-requests:write requested above — creating a + # check run or posting a PR comment there fails with "Resource not accessible by + # integration". Skip both write-dependent reporting steps in that case; the job + # itself still fails (and blocks merge) on a test or coverage failure regardless. - name: Report test results uses: dorny/test-reporter@a43b3a5f7366b97d083190328d2c652e1a8b6aa2 # v3 - if: ${{ !cancelled() }} + if: ${{ !cancelled() && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) }} with: name: Go Tests path: test-results.json @@ -72,9 +77,10 @@ jobs: # No status function here: leaving the implicit success() check in # place means this (and the coverage step it reads outputs from) is # skipped if make test-cover failed, instead of erroring on a missing - # report from a skipped step. + # report from a skipped step. Also skipped for fork PRs — see the + # test-reporter step above for why pull-requests:write isn't usable there. - name: Post coverage report to PR - if: ${{ github.event_name == 'pull_request' }} + if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository }} uses: thollander/actions-comment-pull-request@24bffb9b452ba05a4f3f77933840a6a841d1b32b # v3.0.1 with: comment-tag: coverage-report diff --git a/.github/workflows/integration-test.yml b/.github/workflows/integration-test.yml index ea4175e..2121efa 100644 --- a/.github/workflows/integration-test.yml +++ b/.github/workflows/integration-test.yml @@ -25,14 +25,6 @@ on: permissions: {} -concurrency: - group: integration-test-${{ github.event.workflow_run.head_repository.full_name }}-${{ github.event.workflow_run.head_branch }} - # The live suite provisions fixed-name resources (see integration_test.go TestMain), so - # two concurrent runs against the same org collide. Queue instead of cancelling: a - # cancelled Go test binary skips t.Cleanup and TestMain's post-run deletes, so cancelling - # a run trades a wait for a batch of orphaned live resources. - cancel-in-progress: false - jobs: # Ungated on purpose: downloads and validates the pr-metadata artifact immediately, # before any approval wait. An environment approval can take up to 30 days (GitHub's own @@ -184,6 +176,19 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 25 environment: integration-test # gate: waits for reviewer approval here + concurrency: + # One fixed group for every PR/branch, not keyed by head repo/branch: every run — + # regardless of which fork or branch it came from — hits the same live org (sg0) + # and provisions the same fixed-name resources (see integration_test.go TestMain, + # and e.g. TestLiveCreateVariableV2ReturnsErrorOnDuplicateName, which asserts that + # duplicate names are rejected). A per-branch key would let two approved PRs run + # this job at the same time and collide on those names. + # + # Scoped to this job rather than the whole workflow so a second PR's `resolve` job + # (validation + opening the queued check) still runs immediately and isn't stuck + # behind however long the first PR's environment approval takes to arrive. + group: integration-test-live-suite + cancel-in-progress: false # Deliberately just contents: read — this job runs a fork PR's code with a live API # token in its environment. Reporting the outcome (checks:write, pull-requests:write) # is split into the ungated `report` job below so fork code never runs on a runner that From 9ada2b17552f5a317260fe2ba31734fd0d4edb74 Mon Sep 17 00:00:00 2001 From: Jaideep Padhye Date: Mon, 10 Aug 2026 15:05:27 -0700 Subject: [PATCH 11/13] TEMPORARY: disable integration-test approval gate for E2E testing Commenting out `environment: integration-test` so the author can exercise the full PR Metadata -> resolve -> integration-test -> report flow without waiting on maintainer approval. This removes the pipeline's only defense against running fork-PR code with the live API token and MUST be reverted before merge. --- .github/workflows/integration-test.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/integration-test.yml b/.github/workflows/integration-test.yml index 2121efa..7a4b5db 100644 --- a/.github/workflows/integration-test.yml +++ b/.github/workflows/integration-test.yml @@ -175,7 +175,9 @@ jobs: if: needs.resolve.outputs.found == 'true' runs-on: ubuntu-latest timeout-minutes: 25 - environment: integration-test # gate: waits for reviewer approval here + # TEMPORARY: approval gate disabled for end-to-end testing by the PR author. + # MUST be restored before merge — see follow-up revert commit. + # environment: integration-test # gate: waits for reviewer approval here concurrency: # One fixed group for every PR/branch, not keyed by head repo/branch: every run — # regardless of which fork or branch it came from — hits the same live org (sg0) From ded15833273c92546550695de53de9612584720c Mon Sep 17 00:00:00 2001 From: Jaideep Padhye Date: Mon, 10 Aug 2026 15:28:00 -0700 Subject: [PATCH 12/13] Revert "TEMPORARY: disable integration-test approval gate for E2E testing" This reverts commit 9ada2b17552f5a317260fe2ba31734fd0d4edb74. --- .github/workflows/integration-test.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/integration-test.yml b/.github/workflows/integration-test.yml index 7a4b5db..2121efa 100644 --- a/.github/workflows/integration-test.yml +++ b/.github/workflows/integration-test.yml @@ -175,9 +175,7 @@ jobs: if: needs.resolve.outputs.found == 'true' runs-on: ubuntu-latest timeout-minutes: 25 - # TEMPORARY: approval gate disabled for end-to-end testing by the PR author. - # MUST be restored before merge — see follow-up revert commit. - # environment: integration-test # gate: waits for reviewer approval here + environment: integration-test # gate: waits for reviewer approval here concurrency: # One fixed group for every PR/branch, not keyed by head repo/branch: every run — # regardless of which fork or branch it came from — hits the same live org (sg0) From 87d564c33416093b31691a1349b5e8a22a6f2c38 Mon Sep 17 00:00:00 2001 From: Jaideep Padhye Date: Tue, 11 Aug 2026 17:07:11 -0700 Subject: [PATCH 13/13] Thread check-run ID through job outputs instead of re-querying by ref report was resolving the check run to complete via checks.listForRef(...) .check_runs[0], which has no guarantee of returning the run created by this specific workflow run. A rerun of PR Metadata or this workflow for the same head SHA creates a second integration-test check run, and the wrong one could get completed, leaving the other stuck at queued. --- .github/workflows/integration-test.yml | 29 ++++++++++++++++---------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/.github/workflows/integration-test.yml b/.github/workflows/integration-test.yml index 2121efa..2f8b869 100644 --- a/.github/workflows/integration-test.yml +++ b/.github/workflows/integration-test.yml @@ -43,6 +43,7 @@ jobs: found: ${{ steps.pr.outputs.found }} number: ${{ steps.pr.outputs.number }} sha: ${{ steps.pr.outputs.sha }} + check-run-id: ${{ steps.create-check.outputs.id }} steps: - name: Download PR metadata @@ -154,13 +155,20 @@ jobs: core.setOutput('sha', trustedSha); - name: Create pending check run + id: create-check if: steps.pr.outputs.found == 'true' uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 env: PR_SHA: ${{ steps.pr.outputs.sha }} with: script: | - await github.rest.checks.create({ + // Capture the created check run's own ID as a job output rather than having + // `report` re-look it up by ref+name later: a rerun of this workflow (or of + // PR Metadata) for the same head SHA would create a second `integration-test` + // check run, and listForRef().check_runs[0] has no guarantee of returning + // *this* run's check — it could complete a different run's check and leave + // this one stuck at queued. + const { data: check } = await github.rest.checks.create({ owner: context.repo.owner, repo: context.repo.repo, name: 'integration-test', @@ -168,6 +176,7 @@ jobs: status: 'queued', details_url: `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`, }); + core.setOutput('id', String(check.id)); integration-test: name: integration-test @@ -273,7 +282,7 @@ jobs: uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 env: JOB_RESULT: ${{ needs.integration-test.result }} - PR_SHA: ${{ needs.resolve.outputs.sha }} + CHECK_RUN_ID: ${{ needs.resolve.outputs.check-run-id }} PR_NUMBER: ${{ needs.resolve.outputs.number }} with: script: | @@ -320,18 +329,16 @@ jobs: } } - const checks = await github.rest.checks.listForRef({ - owner: context.repo.owner, - repo: context.repo.repo, - ref: process.env.PR_SHA, - check_name: 'integration-test', - }); - const check = checks.data.check_runs[0]; - if (check) { + // Update the exact check run `resolve` created (its ID was threaded through as + // a job output), not a re-lookup by ref+name: a rerun of this workflow for the + // same head SHA would create a second `integration-test` check run, and + // listForRef().check_runs[0] has no guarantee of returning *this* run's check — + // it could complete a different run's check and leave this one stuck at queued. + if (process.env.CHECK_RUN_ID) { await github.rest.checks.update({ owner: context.repo.owner, repo: context.repo.repo, - check_run_id: check.id, + check_run_id: Number(process.env.CHECK_RUN_ID), status: 'completed', conclusion, details_url: runUrl,