diff --git a/syntheticsclientv2/common_models.go b/syntheticsclientv2/common_models.go index 299a6bf..c6dd41e 100644 --- a/syntheticsclientv2/common_models.go +++ b/syntheticsclientv2/common_models.go @@ -15,10 +15,79 @@ package syntheticsclientv2 import ( + "encoding/json" "time" ) // Common and shared struct models used for more complex requests +type NullableString struct { + Value *string +} + +func NewNullableString(value string) *NullableString { + return &NullableString{Value: &value} +} + +func NewNullString() *NullableString { + return &NullableString{} +} + +func (n NullableString) MarshalJSON() ([]byte, error) { + if n.Value == nil { + return []byte("null"), nil + } + + return json.Marshal(*n.Value) +} + +func (n *NullableString) UnmarshalJSON(data []byte) error { + if string(data) == "null" { + n.Value = nil + return nil + } + + var value string + if err := json.Unmarshal(data, &value); err != nil { + return err + } + n.Value = &value + return nil +} + +type NullableInt struct { + Value *int +} + +func NewNullableInt(value int) *NullableInt { + return &NullableInt{Value: &value} +} + +func NewNullInt() *NullableInt { + return &NullableInt{} +} + +func (n NullableInt) MarshalJSON() ([]byte, error) { + if n.Value == nil { + return []byte("null"), nil + } + + return json.Marshal(*n.Value) +} + +func (n *NullableInt) UnmarshalJSON(data []byte) error { + if string(data) == "null" { + n.Value = nil + return nil + } + + var value int + if err := json.Unmarshal(data, &value); err != nil { + return err + } + n.Value = &value + return nil +} + type Networkconnection struct { Description string `json:"description,omitempty"` Downloadbandwidth int `json:"downloadBandwidth,omitempty"` @@ -314,6 +383,120 @@ type ChecksV2Response struct { Totalcount int `json:"totalCount"` } +type CaCertificate struct { + ID int `json:"id,omitempty"` + Name string `json:"name"` + Description string `json:"description,omitempty"` + Content string `json:"content,omitempty"` + FileExtension string `json:"fileExtension,omitempty"` + Filename string `json:"filename,omitempty"` + ExpiresAt time.Time `json:"expiresAt,omitempty"` + CreatedAt time.Time `json:"createdAt,omitempty"` + CreatedBy string `json:"createdBy,omitempty"` + UpdatedAt time.Time `json:"updatedAt,omitempty"` + UpdatedBy string `json:"updatedBy,omitempty"` +} + +type CaCertificateInput struct { + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + Content string `json:"content,omitempty"` + FileExtension string `json:"fileExtension,omitempty"` + Filename string `json:"filename,omitempty"` +} + +type CaCertificateUpdateInput struct { + Name *string `json:"name,omitempty"` + Description *string `json:"description,omitempty"` + Content *string `json:"content,omitempty"` + FileExtension *string `json:"fileExtension,omitempty"` + Filename *string `json:"filename,omitempty"` +} + +type CaCertificateV2Input struct { + CaCert CaCertificateInput `json:"cacert"` +} + +type CaCertificateV2UpdateInput struct { + CaCert CaCertificateUpdateInput `json:"cacert"` +} + +type CaCertificateV2Response struct { + CaCert CaCertificate `json:"cacert"` +} + +type CaCertificatesV2Response struct { + CaCerts []CaCertificate `json:"cacerts"` +} + +type SslCheckV2Response struct { + Test struct { + ID int `json:"id,omitempty"` + Name string `json:"name,omitempty"` + Active bool `json:"active"` + Frequency int `json:"frequency,omitempty"` + SchedulingStrategy string `json:"schedulingStrategy,omitempty"` + CreatedAt time.Time `json:"createdAt,omitempty"` + UpdatedAt time.Time `json:"updatedAt,omitempty"` + LocationIds []string `json:"locationIds,omitempty"` + Type string `json:"type,omitempty"` + Host string `json:"host,omitempty"` + Port int `json:"port,omitempty"` + ServerName *string `json:"serverName,omitempty"` + AllowSelfSigned bool `json:"allowSelfSigned"` + AllowUntrustedRoot bool `json:"allowUntrustedRoot"` + CaCertificateID *int `json:"caCertificateId"` + Validations []Validations `json:"validations"` + Customproperties []CustomProperties `json:"customProperties"` + Lastrunstatus string `json:"lastRunStatus"` + Lastrunat time.Time `json:"lastRunAt"` + LastRunCoreMetricsPublishedAt time.Time `json:"lastRunCoreMetricsPublishedAt"` + LastRunLocationId string `json:"lastRunLocationId"` + LastRunId int `json:"lastRunId"` + Automaticretries int `json:"automaticRetries"` + Createdby string `json:"createdBy"` + Updatedby string `json:"updatedBy"` + } `json:"test"` +} + +type SslCheckV2Input struct { + Test struct { + Name string `json:"name"` + LocationIds []string `json:"locationIds"` + Frequency int `json:"frequency"` + SchedulingStrategy string `json:"schedulingStrategy"` + Active bool `json:"active"` + Customproperties []CustomProperties `json:"customProperties"` + Automaticretries int `json:"automaticRetries"` + Host string `json:"host"` + Port int `json:"port"` + ServerName *string `json:"serverName"` + AllowSelfSigned bool `json:"allowSelfSigned"` + AllowUntrustedRoot bool `json:"allowUntrustedRoot"` + CaCertificateID *int `json:"caCertificateId"` + Validations []Validations `json:"validations"` + } `json:"test"` +} + +type SslCheckV2UpdateInput struct { + Test struct { + Name *string `json:"name,omitempty"` + LocationIds *[]string `json:"locationIds,omitempty"` + Frequency *int `json:"frequency,omitempty"` + SchedulingStrategy *string `json:"schedulingStrategy,omitempty"` + Active *bool `json:"active,omitempty"` + Customproperties *[]CustomProperties `json:"customProperties,omitempty"` + Automaticretries *int `json:"automaticRetries,omitempty"` + Host *string `json:"host,omitempty"` + Port *int `json:"port,omitempty"` + ServerName *NullableString `json:"serverName,omitempty"` + AllowSelfSigned *bool `json:"allowSelfSigned,omitempty"` + AllowUntrustedRoot *bool `json:"allowUntrustedRoot,omitempty"` + CaCertificateID *NullableInt `json:"caCertificateId,omitempty"` + Validations *[]Validations `json:"validations,omitempty"` + } `json:"test"` +} + type PortCheckV2Response struct { Test struct { ID int `json:"id"` diff --git a/syntheticsclientv2/create_cacertificatev2.go b/syntheticsclientv2/create_cacertificatev2.go new file mode 100644 index 0000000..1fd6056 --- /dev/null +++ b/syntheticsclientv2/create_cacertificatev2.go @@ -0,0 +1,50 @@ +// 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 ( + "bytes" + "encoding/json" +) + +func parseCreateCaCertificateV2Response(response string) (*CaCertificateV2Response, error) { + var createCaCertificateV2 CaCertificateV2Response + JSONResponse := []byte(response) + err := json.Unmarshal(JSONResponse, &createCaCertificateV2) + if err != nil { + return nil, err + } + + return &createCaCertificateV2, err +} + +func (c Client) CreateCaCertificateV2(CaCertificateV2Details *CaCertificateV2Input) (*CaCertificateV2Response, *RequestDetails, error) { + body, err := json.Marshal(CaCertificateV2Details) + if err != nil { + return nil, nil, err + } + + details, err := c.makePublicAPICall("POST", "/cacerts", bytes.NewBuffer(body), nil) + if err != nil { + return nil, details, err + } + + newCaCertificateV2, err := parseCreateCaCertificateV2Response(details.ResponseBody) + if err != nil { + return newCaCertificateV2, details, err + } + + return newCaCertificateV2, details, nil +} diff --git a/syntheticsclientv2/create_cacertificatev2_test.go b/syntheticsclientv2/create_cacertificatev2_test.go new file mode 100644 index 0000000..8c8c512 --- /dev/null +++ b/syntheticsclientv2/create_cacertificatev2_test.go @@ -0,0 +1,124 @@ +//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" + "io" + "net/http" + "reflect" + "testing" +) + +var ( + createCaCertificateV2Body = `{"cacert":{"name":"foo","description":"My CA certificate","content":"Q2VydGlmaWNhdGU=","fileExtension":"pem","filename":"ca_cert_file"}}` + createCaCertificateV2ResponseBody = `{"cacert":{"id":1,"name":"foo","description":"My CA certificate","content":"","fileExtension":"pem","filename":"ca_cert_file","expiresAt":"2026-09-14T14:35:37.801Z","createdAt":"2022-09-14T14:35:37.801Z","createdBy":"abcdefgh1234","updatedAt":"2022-09-14T14:35:38.099Z","updatedBy":"abcdefgh1234"}}` + inputCaCertificateV2Data = CaCertificateV2Input{} + outputCaCertificateV2Data = CaCertificateV2Response{} +) + +func TestCreateCaCertificateV2(t *testing.T) { + setup() + defer teardown() + + testMux.HandleFunc("/cacerts", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + + requestBody, err := io.ReadAll(r.Body) + if err != nil { + t.Fatal(err) + } + + var requestEnvelope map[string]json.RawMessage + err = json.Unmarshal(requestBody, &requestEnvelope) + if err != nil { + t.Fatal(err) + } + if len(requestEnvelope) != 1 { + t.Fatalf("request body envelope keys \n\n%#v want only cacert", requestEnvelope) + } + rawCaCert, ok := requestEnvelope["cacert"] + if !ok { + t.Fatal("request body missing cacert envelope") + } + + var requestCaCertFields map[string]json.RawMessage + err = json.Unmarshal(rawCaCert, &requestCaCertFields) + if err != nil { + t.Fatal(err) + } + writableFields := []string{"name", "description", "content", "fileExtension", "filename"} + if len(requestCaCertFields) != len(writableFields) { + t.Errorf("request body cacert field count \n\n%#v want \n\n%#v", len(requestCaCertFields), len(writableFields)) + } + for _, field := range writableFields { + if _, ok := requestCaCertFields[field]; !ok { + t.Errorf("request body missing cacert.%s", field) + } + } + for _, field := range []string{"id", "expiresAt", "createdAt", "createdBy", "updatedAt", "updatedBy"} { + if _, ok := requestCaCertFields[field]; ok { + t.Errorf("request body includes response-only cacert.%s", field) + } + } + + requestCaCertificateV2Data := CaCertificateV2Input{} + err = json.Unmarshal(requestBody, &requestCaCertificateV2Data) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(requestCaCertificateV2Data.CaCert.Name, inputCaCertificateV2Data.CaCert.Name) { + t.Errorf("request body name \n\n%#v want \n\n%#v", requestCaCertificateV2Data.CaCert.Name, inputCaCertificateV2Data.CaCert.Name) + } + if !reflect.DeepEqual(requestCaCertificateV2Data.CaCert.Description, inputCaCertificateV2Data.CaCert.Description) { + t.Errorf("request body description \n\n%#v want \n\n%#v", requestCaCertificateV2Data.CaCert.Description, inputCaCertificateV2Data.CaCert.Description) + } + if !reflect.DeepEqual(requestCaCertificateV2Data.CaCert.Content, inputCaCertificateV2Data.CaCert.Content) { + t.Errorf("request body content \n\n%#v want \n\n%#v", requestCaCertificateV2Data.CaCert.Content, inputCaCertificateV2Data.CaCert.Content) + } + if !reflect.DeepEqual(requestCaCertificateV2Data.CaCert.FileExtension, inputCaCertificateV2Data.CaCert.FileExtension) { + t.Errorf("request body file extension \n\n%#v want \n\n%#v", requestCaCertificateV2Data.CaCert.FileExtension, inputCaCertificateV2Data.CaCert.FileExtension) + } + if !reflect.DeepEqual(requestCaCertificateV2Data.CaCert.Filename, inputCaCertificateV2Data.CaCert.Filename) { + t.Errorf("request body filename \n\n%#v want \n\n%#v", requestCaCertificateV2Data.CaCert.Filename, inputCaCertificateV2Data.CaCert.Filename) + } + + _, err = w.Write([]byte(createCaCertificateV2ResponseBody)) + if err != nil { + t.Fatal(err) + } + }) + + err := json.Unmarshal([]byte(createCaCertificateV2Body), &inputCaCertificateV2Data) + if err != nil { + t.Fatal(err) + } + err = json.Unmarshal([]byte(createCaCertificateV2ResponseBody), &outputCaCertificateV2Data) + if err != nil { + t.Fatal(err) + } + + resp, _, err := testClient.CreateCaCertificateV2(&inputCaCertificateV2Data) + if err != nil { + t.Fatal(err) + } + + if !reflect.DeepEqual(resp.CaCert, outputCaCertificateV2Data.CaCert) { + t.Errorf("returned \n\n%#v want \n\n%#v", resp.CaCert, outputCaCertificateV2Data.CaCert) + } +} diff --git a/syntheticsclientv2/create_sslcheckv2.go b/syntheticsclientv2/create_sslcheckv2.go new file mode 100644 index 0000000..fcf7e64 --- /dev/null +++ b/syntheticsclientv2/create_sslcheckv2.go @@ -0,0 +1,55 @@ +// 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 ( + "bytes" + "encoding/json" +) + +func parseCreateSslCheckV2Response(response string) (*SslCheckV2Response, error) { + var createSslCheckV2 SslCheckV2Response + JSONResponse := []byte(response) + err := json.Unmarshal(JSONResponse, &createSslCheckV2) + if err != nil { + return nil, err + } + + return &createSslCheckV2, err +} + +func (c Client) CreateSslCheckV2(SslCheckV2Details *SslCheckV2Input) (*SslCheckV2Response, *RequestDetails, error) { + if SslCheckV2Details.Test.Validations == nil { + validation := make([]Validations, 0) + SslCheckV2Details.Test.Validations = validation + } + + body, err := json.Marshal(SslCheckV2Details) + if err != nil { + return nil, nil, err + } + + details, err := c.makePublicAPICall("POST", "/tests/ssl", bytes.NewBuffer(body), nil) + if err != nil { + return nil, details, err + } + + newSslCheckV2, err := parseCreateSslCheckV2Response(details.ResponseBody) + if err != nil { + return newSslCheckV2, details, err + } + + return newSslCheckV2, details, nil +} diff --git a/syntheticsclientv2/create_sslcheckv2_test.go b/syntheticsclientv2/create_sslcheckv2_test.go new file mode 100644 index 0000000..679d0ce --- /dev/null +++ b/syntheticsclientv2/create_sslcheckv2_test.go @@ -0,0 +1,286 @@ +//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" + "io" + "net/http" + "reflect" + "strings" + "testing" +) + +var ( + createSslCheckV2Body = `{"test":{"name":"ssl-check","frequency":5,"schedulingStrategy":"round_robin","active":true,"locationIds":["aws-us-east-1"],"customProperties":[{"key":"env","value":"prod"}],"automaticRetries":1,"host":"www.splunk.com","port":443,"serverName":"www.splunk.com","allowSelfSigned":true,"allowUntrustedRoot":false,"caCertificateId":42,"validations":[{"name":"Certificate expires later","type":"assert_numeric","actual":"{{certificate.days_until_expiration}}","expected":"30","comparator":"is_greater_than"}]}}` + inputSslCheckV2Data = SslCheckV2Input{} +) + +func TestCreateSslCheckV2(t *testing.T) { + setup() + defer teardown() + + expectedCaCertificateID := 42 + expectedValidations := []Validations{ + { + Name: "Certificate expires later", + Type: "assert_numeric", + Actual: "{{certificate.days_until_expiration}}", + Expected: "30", + Comparator: "is_greater_than", + }, + } + expectedCustomProperties := []CustomProperties{ + { + Key: "env", + Value: "prod", + }, + } + + testMux.HandleFunc("/tests/ssl", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + requestBody, err := io.ReadAll(r.Body) + if err != nil { + t.Fatal(err) + } + + var requestEnvelope map[string]json.RawMessage + err = json.Unmarshal(requestBody, &requestEnvelope) + if err != nil { + t.Fatal(err) + } + rawTest, ok := requestEnvelope["test"] + if !ok { + t.Fatal("request body missing test envelope") + } + + var requestTestFields map[string]json.RawMessage + err = json.Unmarshal(rawTest, &requestTestFields) + if err != nil { + t.Fatal(err) + } + for _, field := range []string{"host", "port", "serverName", "allowSelfSigned", "allowUntrustedRoot", "caCertificateId", "validations", "customProperties"} { + if _, ok := requestTestFields[field]; !ok { + t.Errorf("request body missing test.%s", field) + } + } + + requestSslCheckV2Data := SslCheckV2Input{} + err = json.Unmarshal(requestBody, &requestSslCheckV2Data) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(requestSslCheckV2Data.Test.Host, "www.splunk.com") { + t.Errorf("request body host \n\n%#v want \n\n%#v", requestSslCheckV2Data.Test.Host, "www.splunk.com") + } + if !reflect.DeepEqual(requestSslCheckV2Data.Test.Port, 443) { + t.Errorf("request body port \n\n%#v want \n\n%#v", requestSslCheckV2Data.Test.Port, 443) + } + assertStringPtr(t, requestSslCheckV2Data.Test.ServerName, "www.splunk.com") + if !reflect.DeepEqual(requestSslCheckV2Data.Test.AllowSelfSigned, true) { + t.Errorf("request body allow self signed \n\n%#v want \n\n%#v", requestSslCheckV2Data.Test.AllowSelfSigned, true) + } + if !reflect.DeepEqual(requestSslCheckV2Data.Test.AllowUntrustedRoot, false) { + t.Errorf("request body allow untrusted root \n\n%#v want \n\n%#v", requestSslCheckV2Data.Test.AllowUntrustedRoot, false) + } + if requestSslCheckV2Data.Test.CaCertificateID == nil || !reflect.DeepEqual(*requestSslCheckV2Data.Test.CaCertificateID, expectedCaCertificateID) { + t.Errorf("request body ca certificate id \n\n%#v want \n\n%#v", requestSslCheckV2Data.Test.CaCertificateID, expectedCaCertificateID) + } + if !reflect.DeepEqual(requestSslCheckV2Data.Test.Validations, expectedValidations) { + t.Errorf("request body validations \n\n%#v want \n\n%#v", requestSslCheckV2Data.Test.Validations, expectedValidations) + } + if !reflect.DeepEqual(requestSslCheckV2Data.Test.Customproperties, expectedCustomProperties) { + t.Errorf("request body custom properties \n\n%#v want \n\n%#v", requestSslCheckV2Data.Test.Customproperties, expectedCustomProperties) + } + + _, err = w.Write([]byte(createSslCheckV2Body)) + if err != nil { + t.Fatal(err) + } + }) + + err := json.Unmarshal([]byte(createSslCheckV2Body), &inputSslCheckV2Data) + if err != nil { + t.Fatal(err) + } + + resp, _, err := testClient.CreateSslCheckV2(&inputSslCheckV2Data) + if err != nil { + t.Fatal(err) + } + + if !reflect.DeepEqual(resp.Test.Name, inputSslCheckV2Data.Test.Name) { + t.Errorf("returned \n\n%#v want \n\n%#v", resp.Test.Name, inputSslCheckV2Data.Test.Name) + } + if !reflect.DeepEqual(resp.Test.Host, inputSslCheckV2Data.Test.Host) { + t.Errorf("returned \n\n%#v want \n\n%#v", resp.Test.Host, inputSslCheckV2Data.Test.Host) + } + if !reflect.DeepEqual(resp.Test.Port, inputSslCheckV2Data.Test.Port) { + t.Errorf("returned \n\n%#v want \n\n%#v", resp.Test.Port, inputSslCheckV2Data.Test.Port) + } + if !reflect.DeepEqual(resp.Test.ServerName, inputSslCheckV2Data.Test.ServerName) { + t.Errorf("returned \n\n%#v want \n\n%#v", resp.Test.ServerName, inputSslCheckV2Data.Test.ServerName) + } + if !reflect.DeepEqual(resp.Test.AllowSelfSigned, inputSslCheckV2Data.Test.AllowSelfSigned) { + t.Errorf("returned \n\n%#v want \n\n%#v", resp.Test.AllowSelfSigned, inputSslCheckV2Data.Test.AllowSelfSigned) + } + if !reflect.DeepEqual(resp.Test.AllowUntrustedRoot, inputSslCheckV2Data.Test.AllowUntrustedRoot) { + t.Errorf("returned \n\n%#v want \n\n%#v", resp.Test.AllowUntrustedRoot, inputSslCheckV2Data.Test.AllowUntrustedRoot) + } + if !reflect.DeepEqual(resp.Test.LocationIds, inputSslCheckV2Data.Test.LocationIds) { + t.Errorf("returned \n\n%#v want \n\n%#v", resp.Test.LocationIds, inputSslCheckV2Data.Test.LocationIds) + } + if !reflect.DeepEqual(resp.Test.Customproperties, inputSslCheckV2Data.Test.Customproperties) { + t.Errorf("returned \n\n%#v want \n\n%#v", resp.Test.Customproperties, inputSslCheckV2Data.Test.Customproperties) + } + if !reflect.DeepEqual(resp.Test.Automaticretries, inputSslCheckV2Data.Test.Automaticretries) { + t.Errorf("returned \n\n%#v want \n\n%#v", resp.Test.Automaticretries, inputSslCheckV2Data.Test.Automaticretries) + } + if !reflect.DeepEqual(resp.Test.CaCertificateID, inputSslCheckV2Data.Test.CaCertificateID) { + t.Errorf("returned \n\n%#v want \n\n%#v", resp.Test.CaCertificateID, inputSslCheckV2Data.Test.CaCertificateID) + } + if !reflect.DeepEqual(resp.Test.Validations, inputSslCheckV2Data.Test.Validations) { + t.Errorf("returned \n\n%#v want \n\n%#v", resp.Test.Validations, inputSslCheckV2Data.Test.Validations) + } +} + +func assertStringPtr(t *testing.T, got *string, want string) { + t.Helper() + + if got == nil { + t.Fatalf("got nil string pointer want \n\n%#v", want) + } + if !reflect.DeepEqual(*got, want) { + t.Errorf("got string pointer value \n\n%#v want \n\n%#v", *got, want) + } +} + +func TestCreateSslCheckV2DefaultsNilValidations(t *testing.T) { + setup() + defer teardown() + + inputSslCheckV2Data := SslCheckV2Input{} + err := json.Unmarshal([]byte(createSslCheckV2Body), &inputSslCheckV2Data) + if err != nil { + t.Fatal(err) + } + inputSslCheckV2Data.Test.Validations = nil + + testMux.HandleFunc("/tests/ssl", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + requestBody, err := io.ReadAll(r.Body) + if err != nil { + t.Fatal(err) + } + + var requestEnvelope map[string]json.RawMessage + err = json.Unmarshal(requestBody, &requestEnvelope) + if err != nil { + t.Fatal(err) + } + rawTest, ok := requestEnvelope["test"] + if !ok { + t.Fatal("request body missing test envelope") + } + + var requestTestFields map[string]json.RawMessage + err = json.Unmarshal(rawTest, &requestTestFields) + if err != nil { + t.Fatal(err) + } + rawValidations, ok := requestTestFields["validations"] + if !ok { + t.Fatal("request body missing test.validations") + } + + var validations []json.RawMessage + err = json.Unmarshal(rawValidations, &validations) + if err != nil { + t.Fatal(err) + } + if validations == nil { + t.Fatalf("request body validations \n\n%#v want empty slice", string(rawValidations)) + } + if len(validations) != 0 { + t.Errorf("request body validations length \n\n%#v want \n\n%#v", len(validations), 0) + } + + _, err = w.Write([]byte(createSslCheckV2Body)) + if err != nil { + t.Fatal(err) + } + }) + + _, _, err = testClient.CreateSslCheckV2(&inputSslCheckV2Data) + if err != nil { + t.Fatal(err) + } +} + +func TestCreateSslCheckV2PreservesNilServerName(t *testing.T) { + setup() + defer teardown() + + inputBody := strings.Replace(createSslCheckV2Body, `"serverName":"www.splunk.com"`, `"serverName":null`, 1) + inputSslCheckV2Data := SslCheckV2Input{} + err := json.Unmarshal([]byte(inputBody), &inputSslCheckV2Data) + if err != nil { + t.Fatal(err) + } + + testMux.HandleFunc("/tests/ssl", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + requestBody, err := io.ReadAll(r.Body) + if err != nil { + t.Fatal(err) + } + + var requestEnvelope map[string]json.RawMessage + err = json.Unmarshal(requestBody, &requestEnvelope) + if err != nil { + t.Fatal(err) + } + rawTest, ok := requestEnvelope["test"] + if !ok { + t.Fatal("request body missing test envelope") + } + + var requestTestFields map[string]json.RawMessage + err = json.Unmarshal(rawTest, &requestTestFields) + if err != nil { + t.Fatal(err) + } + rawServerName, ok := requestTestFields["serverName"] + if !ok { + t.Fatal("request body missing test.serverName") + } + if string(rawServerName) != "null" { + t.Fatalf("request body server name \n\n%#v want JSON null", string(rawServerName)) + } + + _, err = w.Write([]byte(createSslCheckV2Body)) + if err != nil { + t.Fatal(err) + } + }) + + _, _, err = testClient.CreateSslCheckV2(&inputSslCheckV2Data) + if err != nil { + t.Fatal(err) + } +} diff --git a/syntheticsclientv2/delete_cacertificatev2.go b/syntheticsclientv2/delete_cacertificatev2.go new file mode 100644 index 0000000..7ffff08 --- /dev/null +++ b/syntheticsclientv2/delete_cacertificatev2.go @@ -0,0 +1,39 @@ +// 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 ( + "bytes" + "errors" + "fmt" + "strconv" +) + +func (c Client) DeleteCaCertificateV2(id int) (int, error) { + requestDetails, err := c.makePublicAPICall("DELETE", fmt.Sprintf("/cacerts/%d", id), bytes.NewBufferString("{}"), nil) + if err != nil { + return 1, err + } + var status = requestDetails.StatusCode + + fmt.Println(status) + + if status >= 300 || status < 200 { + errorMsg := fmt.Sprintf("error: Response code %v. Expecting 2XX.", strconv.Itoa(status)) + return status, errors.New(errorMsg) + } + + return status, err +} diff --git a/syntheticsclientv2/delete_cacertificatev2_test.go b/syntheticsclientv2/delete_cacertificatev2_test.go new file mode 100644 index 0000000..9c2a625 --- /dev/null +++ b/syntheticsclientv2/delete_cacertificatev2_test.go @@ -0,0 +1,41 @@ +//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 ( + "net/http" + "testing" +) + +func TestDeleteCaCertificateV2(t *testing.T) { + setup() + defer teardown() + + testMux.HandleFunc("/cacerts/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + w.WriteHeader(http.StatusNoContent) + }) + + resp, err := testClient.DeleteCaCertificateV2(1) + if err != nil { + t.Fatal(err) + } + if resp != http.StatusNoContent { + t.Errorf("returned \n\n%#v want \n\n%#v", resp, http.StatusNoContent) + } +} diff --git a/syntheticsclientv2/delete_sslcheckv2.go b/syntheticsclientv2/delete_sslcheckv2.go new file mode 100644 index 0000000..ce743cf --- /dev/null +++ b/syntheticsclientv2/delete_sslcheckv2.go @@ -0,0 +1,39 @@ +// 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 ( + "bytes" + "errors" + "fmt" + "strconv" +) + +func (c Client) DeleteSslCheckV2(id int) (int, error) { + requestDetails, err := c.makePublicAPICall("DELETE", fmt.Sprintf("/tests/ssl/%d", id), bytes.NewBufferString("{}"), nil) + if err != nil { + return 1, err + } + var status = requestDetails.StatusCode + + fmt.Println(status) + + if status >= 300 || status < 200 { + errorMsg := fmt.Sprintf("error: Response code %v. Expecting 2XX.", strconv.Itoa(status)) + return status, errors.New(errorMsg) + } + + return status, err +} diff --git a/syntheticsclientv2/delete_sslcheckv2_test.go b/syntheticsclientv2/delete_sslcheckv2_test.go new file mode 100644 index 0000000..b7d6911 --- /dev/null +++ b/syntheticsclientv2/delete_sslcheckv2_test.go @@ -0,0 +1,41 @@ +//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 ( + "net/http" + "testing" +) + +func TestDeleteSslCheckV2(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.StatusNoContent) + }) + + resp, err := testClient.DeleteSslCheckV2(19) + if err != nil { + t.Fatal(err) + } + if resp != http.StatusNoContent { + t.Errorf("returned \n\n%#v want \n\n%#v", resp, http.StatusNoContent) + } +} diff --git a/syntheticsclientv2/get_cacertificatesv2.go b/syntheticsclientv2/get_cacertificatesv2.go new file mode 100644 index 0000000..791a256 --- /dev/null +++ b/syntheticsclientv2/get_cacertificatesv2.go @@ -0,0 +1,44 @@ +// 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 ( + "bytes" + "encoding/json" +) + +func parseGetCaCertificatesV2Response(response string) (*CaCertificatesV2Response, error) { + var caCertificatesV2 CaCertificatesV2Response + err := json.Unmarshal([]byte(response), &caCertificatesV2) + if err != nil { + return nil, err + } + + return &caCertificatesV2, err +} + +func (c Client) GetCaCertificatesV2() (*CaCertificatesV2Response, *RequestDetails, error) { + details, err := c.makePublicAPICall("GET", "/cacerts", bytes.NewBufferString("{}"), nil) + if err != nil { + return nil, details, err + } + + caCertificatesV2, err := parseGetCaCertificatesV2Response(details.ResponseBody) + if err != nil { + return caCertificatesV2, details, err + } + + return caCertificatesV2, details, nil +} diff --git a/syntheticsclientv2/get_cacertificatesv2_test.go b/syntheticsclientv2/get_cacertificatesv2_test.go new file mode 100644 index 0000000..5b30388 --- /dev/null +++ b/syntheticsclientv2/get_cacertificatesv2_test.go @@ -0,0 +1,61 @@ +//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" + "net/http" + "reflect" + "testing" +) + +var ( + getCaCertificatesV2Body = `{"cacerts":[{"id":1,"name":"foo","description":"My CA certificate","content":"","fileExtension":"pem","filename":"ca_cert_file","expiresAt":"2026-09-14T14:35:37.801Z","createdAt":"2022-09-14T14:35:37.801Z","createdBy":"abcdefgh1234","updatedAt":"2022-09-14T14:35:38.099Z","updatedBy":"abcdefgh1234"},{"id":2,"name":"bar","description":"Another CA certificate","content":"","fileExtension":"crt","filename":"ca_cert_file_2","expiresAt":"2027-09-14T14:35:37.801Z","createdAt":"2022-09-14T14:35:37.801Z","createdBy":"abcdefgh1234","updatedAt":"2022-09-14T14:35:38.099Z","updatedBy":"abcdefgh1234"}]}` + inputGetCaCertificatesV2 = verifyCaCertificatesV2Input(string(getCaCertificatesV2Body)) +) + +func TestGetCaCertificatesV2(t *testing.T) { + setup() + defer teardown() + + testMux.HandleFunc("/cacerts", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + _, err := w.Write([]byte(getCaCertificatesV2Body)) + if err != nil { + t.Fatal(err) + } + }) + + resp, _, err := testClient.GetCaCertificatesV2() + if err != nil { + t.Fatal(err) + } + + if !reflect.DeepEqual(resp.CaCerts, inputGetCaCertificatesV2.CaCerts) { + t.Errorf("returned \n\n%#v want \n\n%#v", resp.CaCerts, inputGetCaCertificatesV2.CaCerts) + } +} + +func verifyCaCertificatesV2Input(stringInput string) *CaCertificatesV2Response { + check := &CaCertificatesV2Response{} + err := json.Unmarshal([]byte(stringInput), check) + if err != nil { + panic(err) + } + return check +} diff --git a/syntheticsclientv2/get_cacertificatev2.go b/syntheticsclientv2/get_cacertificatev2.go new file mode 100644 index 0000000..a5d8b57 --- /dev/null +++ b/syntheticsclientv2/get_cacertificatev2.go @@ -0,0 +1,45 @@ +// 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 ( + "bytes" + "encoding/json" + "fmt" +) + +func parseGetCaCertificateV2Response(response string) (*CaCertificateV2Response, error) { + var caCertificateV2 CaCertificateV2Response + err := json.Unmarshal([]byte(response), &caCertificateV2) + if err != nil { + return nil, err + } + + return &caCertificateV2, err +} + +func (c Client) GetCaCertificateV2(id int) (*CaCertificateV2Response, *RequestDetails, error) { + details, err := c.makePublicAPICall("GET", fmt.Sprintf("/cacerts/%d", id), bytes.NewBufferString("{}"), nil) + if err != nil { + return nil, details, err + } + + caCertificateV2, err := parseGetCaCertificateV2Response(details.ResponseBody) + if err != nil { + return caCertificateV2, details, err + } + + return caCertificateV2, details, nil +} diff --git a/syntheticsclientv2/get_cacertificatev2_test.go b/syntheticsclientv2/get_cacertificatev2_test.go new file mode 100644 index 0000000..c3fdc5a --- /dev/null +++ b/syntheticsclientv2/get_cacertificatev2_test.go @@ -0,0 +1,61 @@ +//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" + "net/http" + "reflect" + "testing" +) + +var ( + getCaCertificateV2Body = `{"cacert":{"id":1,"name":"foo","description":"My CA certificate","content":"","fileExtension":"pem","filename":"ca_cert_file","expiresAt":"2026-09-14T14:35:37.801Z","createdAt":"2022-09-14T14:35:37.801Z","createdBy":"abcdefgh1234","updatedAt":"2022-09-14T14:35:38.099Z","updatedBy":"abcdefgh1234"}}` + inputGetCaCertificateV2 = verifyCaCertificateV2Input(string(getCaCertificateV2Body)) +) + +func TestGetCaCertificateV2(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(getCaCertificateV2Body)) + if err != nil { + t.Fatal(err) + } + }) + + resp, _, err := testClient.GetCaCertificateV2(1) + if err != nil { + t.Fatal(err) + } + + if !reflect.DeepEqual(resp.CaCert, inputGetCaCertificateV2.CaCert) { + t.Errorf("returned \n\n%#v want \n\n%#v", resp.CaCert, inputGetCaCertificateV2.CaCert) + } +} + +func verifyCaCertificateV2Input(stringInput string) *CaCertificateV2Response { + check := &CaCertificateV2Response{} + err := json.Unmarshal([]byte(stringInput), check) + if err != nil { + panic(err) + } + return check +} diff --git a/syntheticsclientv2/get_sslcheckv2.go b/syntheticsclientv2/get_sslcheckv2.go new file mode 100644 index 0000000..5606b44 --- /dev/null +++ b/syntheticsclientv2/get_sslcheckv2.go @@ -0,0 +1,45 @@ +// 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 ( + "bytes" + "encoding/json" + "fmt" +) + +func parseGetSslCheckV2Response(response string) (*SslCheckV2Response, error) { + var sslCheckV2 SslCheckV2Response + err := json.Unmarshal([]byte(response), &sslCheckV2) + if err != nil { + return nil, err + } + + return &sslCheckV2, err +} + +func (c Client) GetSslCheckV2(id int) (*SslCheckV2Response, *RequestDetails, error) { + details, err := c.makePublicAPICall("GET", fmt.Sprintf("/tests/ssl/%d", id), bytes.NewBufferString("{}"), nil) + if err != nil { + return nil, details, err + } + + sslCheckV2, err := parseGetSslCheckV2Response(details.ResponseBody) + if err != nil { + return sslCheckV2, details, err + } + + return sslCheckV2, details, nil +} diff --git a/syntheticsclientv2/get_sslcheckv2_test.go b/syntheticsclientv2/get_sslcheckv2_test.go new file mode 100644 index 0000000..9fa6ef9 --- /dev/null +++ b/syntheticsclientv2/get_sslcheckv2_test.go @@ -0,0 +1,113 @@ +//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" + "net/http" + "reflect" + "strings" + "testing" +) + +var ( + getSslCheckV2Body = `{"test":{"id":1647,"type":"ssl","name":"ssl-check","frequency":5,"schedulingStrategy":"round_robin","active":true,"locationIds":["aws-us-east-1"],"createdAt":"2022-09-14T14:35:37.801Z","updatedAt":"2022-09-14T14:35:38.099Z","createdBy":"abc1234","updatedBy":"abc1234","customProperties":[{"key":"env","value":"prod"}],"automaticRetries":1,"lastRunStatus":"success","lastRunAt":"2024-03-07T00:47:43.741Z","lastRunCoreMetricsPublishedAt":"2024-03-07T00:47:43.741Z","lastRunLocationId":"aws-us-east-1","lastRunId":999,"host":"www.splunk.com","port":443,"serverName":"www.splunk.com","allowSelfSigned":true,"allowUntrustedRoot":false,"caCertificateId":42,"validations":[{"name":"Certificate expires later","type":"assert_numeric","actual":"{{certificate.days_until_expiration}}","expected":"30","comparator":"is_greater_than"}]}}` + inputGetSslCheckV2 = verifySslCheckV2Input(string(getSslCheckV2Body)) +) + +func TestGetSslCheckV2(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(getSslCheckV2Body)) + if err != nil { + t.Fatal(err) + } + }) + + resp, _, err := testClient.GetSslCheckV2(1) + if err != nil { + t.Fatal(err) + } + + if !reflect.DeepEqual(resp.Test.ID, inputGetSslCheckV2.Test.ID) { + t.Errorf("returned \n\n%#v want \n\n%#v", resp.Test.ID, inputGetSslCheckV2.Test.ID) + } + if !reflect.DeepEqual(resp.Test.Name, inputGetSslCheckV2.Test.Name) { + t.Errorf("returned \n\n%#v want \n\n%#v", resp.Test.Name, inputGetSslCheckV2.Test.Name) + } + if !reflect.DeepEqual(resp.Test.Type, inputGetSslCheckV2.Test.Type) { + t.Errorf("returned \n\n%#v want \n\n%#v", resp.Test.Type, inputGetSslCheckV2.Test.Type) + } + if !reflect.DeepEqual(resp.Test.Host, inputGetSslCheckV2.Test.Host) { + t.Errorf("returned \n\n%#v want \n\n%#v", resp.Test.Host, inputGetSslCheckV2.Test.Host) + } + if !reflect.DeepEqual(resp.Test.Port, inputGetSslCheckV2.Test.Port) { + t.Errorf("returned \n\n%#v want \n\n%#v", resp.Test.Port, inputGetSslCheckV2.Test.Port) + } + if !reflect.DeepEqual(resp.Test.ServerName, inputGetSslCheckV2.Test.ServerName) { + t.Errorf("returned \n\n%#v want \n\n%#v", resp.Test.ServerName, inputGetSslCheckV2.Test.ServerName) + } + if !reflect.DeepEqual(resp.Test.AllowSelfSigned, inputGetSslCheckV2.Test.AllowSelfSigned) { + t.Errorf("returned \n\n%#v want \n\n%#v", resp.Test.AllowSelfSigned, inputGetSslCheckV2.Test.AllowSelfSigned) + } + if !reflect.DeepEqual(resp.Test.AllowUntrustedRoot, inputGetSslCheckV2.Test.AllowUntrustedRoot) { + t.Errorf("returned \n\n%#v want \n\n%#v", resp.Test.AllowUntrustedRoot, inputGetSslCheckV2.Test.AllowUntrustedRoot) + } + if !reflect.DeepEqual(resp.Test.CaCertificateID, inputGetSslCheckV2.Test.CaCertificateID) { + t.Errorf("returned \n\n%#v want \n\n%#v", resp.Test.CaCertificateID, inputGetSslCheckV2.Test.CaCertificateID) + } + if !reflect.DeepEqual(resp.Test.Validations, inputGetSslCheckV2.Test.Validations) { + t.Errorf("returned \n\n%#v want \n\n%#v", resp.Test.Validations, inputGetSslCheckV2.Test.Validations) + } +} + +func TestGetSslCheckV2PreservesNilServerName(t *testing.T) { + setup() + defer teardown() + + responseBody := strings.Replace(getSslCheckV2Body, `"serverName":"www.splunk.com"`, `"serverName":null`, 1) + + testMux.HandleFunc("/tests/ssl/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + _, err := w.Write([]byte(responseBody)) + if err != nil { + t.Fatal(err) + } + }) + + resp, _, err := testClient.GetSslCheckV2(1) + if err != nil { + t.Fatal(err) + } + + if resp.Test.ServerName != nil { + t.Fatalf("returned server name \n\n%#v want nil", resp.Test.ServerName) + } +} + +func verifySslCheckV2Input(stringInput string) *SslCheckV2Response { + check := &SslCheckV2Response{} + err := json.Unmarshal([]byte(stringInput), check) + if err != nil { + panic(err) + } + return check +} diff --git a/syntheticsclientv2/update_cacertificatev2.go b/syntheticsclientv2/update_cacertificatev2.go new file mode 100644 index 0000000..b213d58 --- /dev/null +++ b/syntheticsclientv2/update_cacertificatev2.go @@ -0,0 +1,52 @@ +// 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 ( + "bytes" + "encoding/json" + "fmt" +) + +func parseUpdateCaCertificateV2Response(response string) (*CaCertificateV2Response, error) { + var updateCaCertificateV2 CaCertificateV2Response + if response != "" { + err := json.Unmarshal([]byte(response), &updateCaCertificateV2) + if err != nil { + return nil, err + } + return &updateCaCertificateV2, err + } + return &updateCaCertificateV2, nil +} + +func (c Client) UpdateCaCertificateV2(id int, CaCertificateV2Details *CaCertificateV2UpdateInput) (*CaCertificateV2Response, *RequestDetails, error) { + body, err := json.Marshal(CaCertificateV2Details) + if err != nil { + return nil, nil, err + } + + requestDetails, err := c.makePublicAPICall("PUT", fmt.Sprintf("/cacerts/%d", id), bytes.NewBuffer(body), nil) + if err != nil { + return nil, requestDetails, err + } + + updateCaCertificateV2, err := parseUpdateCaCertificateV2Response(requestDetails.ResponseBody) + if err != nil { + return updateCaCertificateV2, requestDetails, err + } + + return updateCaCertificateV2, requestDetails, nil +} diff --git a/syntheticsclientv2/update_cacertificatev2_test.go b/syntheticsclientv2/update_cacertificatev2_test.go new file mode 100644 index 0000000..1ca927a --- /dev/null +++ b/syntheticsclientv2/update_cacertificatev2_test.go @@ -0,0 +1,175 @@ +//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" + "io" + "net/http" + "testing" +) + +var ( + updateCaCertificateV2Body = `{"cacert":{"description":"Updated CA certificate","content":"Q2VydGlmaWNhdGU=","fileExtension":"pem","filename":"updated_ca_cert_file"}}` + inputCaCertificateV2Update = CaCertificateV2UpdateInput{} +) + +func TestUpdateCaCertificateV2(t *testing.T) { + setup() + defer teardown() + + testMux.HandleFunc("/cacerts/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + assertCaCertificateUpdateRequestBody(t, r) + w.WriteHeader(http.StatusOK) + }) + + 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 blank successful update body") + } +} + +func TestUpdateCaCertificateV2AllowsEmptyDescription(t *testing.T) { + setup() + defer teardown() + + description := "" + inputCaCertificateV2Update := CaCertificateV2UpdateInput{} + inputCaCertificateV2Update.CaCert.Description = &description + + testMux.HandleFunc("/cacerts/2", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + assertSingleCaCertificateUpdateField(t, r, "description", `""`) + w.WriteHeader(http.StatusOK) + }) + + resp, _, err := testClient.UpdateCaCertificateV2(2, &inputCaCertificateV2Update) + if err != nil { + t.Fatal(err) + } + if resp == nil { + t.Fatal("expected non-nil response for blank successful update body") + } +} + +func TestUpdateCaCertificateV2AllowsNameUpdate(t *testing.T) { + setup() + defer teardown() + + name := "updated-ca-cert-name" + inputCaCertificateV2Update := CaCertificateV2UpdateInput{} + inputCaCertificateV2Update.CaCert.Name = &name + + testMux.HandleFunc("/cacerts/3", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + assertSingleCaCertificateUpdateField(t, r, "name", `"updated-ca-cert-name"`) + w.WriteHeader(http.StatusOK) + }) + + resp, _, err := testClient.UpdateCaCertificateV2(3, &inputCaCertificateV2Update) + if err != nil { + t.Fatal(err) + } + if resp == nil { + t.Fatal("expected non-nil response for blank successful update body") + } +} + +func assertCaCertificateUpdateRequestBody(t *testing.T, r *http.Request) { + t.Helper() + + _, requestCaCertFields := readCaCertificateUpdateRequestFields(t, r) + + expectedFields := map[string]string{ + "description": `"Updated CA certificate"`, + "content": `"Q2VydGlmaWNhdGU="`, + "fileExtension": `"pem"`, + "filename": `"updated_ca_cert_file"`, + } + if len(requestCaCertFields) != len(expectedFields) { + t.Errorf("request body cacert field count %d want %d", len(requestCaCertFields), len(expectedFields)) + } + for field, expected := range expectedFields { + rawValue, ok := requestCaCertFields[field] + if !ok { + t.Errorf("request body missing cacert.%s", field) + continue + } + if string(rawValue) != expected { + t.Errorf("request body cacert.%s %s want %s", field, rawValue, expected) + } + } + for _, field := range []string{"name", "id", "expiresAt", "createdAt", "createdBy", "updatedAt", "updatedBy"} { + if _, ok := requestCaCertFields[field]; ok { + t.Errorf("request body should not include cacert.%s", field) + } + } +} + +func assertSingleCaCertificateUpdateField(t *testing.T, r *http.Request, field string, expected string) { + t.Helper() + + requestBody, requestCaCertFields := readCaCertificateUpdateRequestFields(t, r) + if len(requestCaCertFields) != 1 { + t.Fatalf("request body cacert field count %d want 1: %s", len(requestCaCertFields), requestBody) + } + + rawValue, ok := requestCaCertFields[field] + if !ok { + t.Fatalf("request body missing cacert.%s", field) + } + if string(rawValue) != expected { + t.Fatalf("request body cacert.%s %s want %s", field, rawValue, expected) + } +} + +func readCaCertificateUpdateRequestFields(t *testing.T, r *http.Request) ([]byte, map[string]json.RawMessage) { + t.Helper() + + requestBody, err := io.ReadAll(r.Body) + if err != nil { + t.Fatal(err) + } + + var requestEnvelope map[string]json.RawMessage + err = json.Unmarshal(requestBody, &requestEnvelope) + if err != nil { + t.Fatal(err) + } + rawCaCert, ok := requestEnvelope["cacert"] + if !ok { + t.Fatal("request body missing cacert envelope") + } + + var requestCaCertFields map[string]json.RawMessage + err = json.Unmarshal(rawCaCert, &requestCaCertFields) + if err != nil { + t.Fatal(err) + } + + return requestBody, requestCaCertFields +} diff --git a/syntheticsclientv2/update_sslcheckv2.go b/syntheticsclientv2/update_sslcheckv2.go new file mode 100644 index 0000000..4e0ac3f --- /dev/null +++ b/syntheticsclientv2/update_sslcheckv2.go @@ -0,0 +1,52 @@ +// 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 ( + "bytes" + "encoding/json" + "fmt" +) + +func parseUpdateSslCheckV2Response(response string) (*SslCheckV2Response, error) { + var updateSslCheckV2 SslCheckV2Response + if response != "" { + err := json.Unmarshal([]byte(response), &updateSslCheckV2) + if err != nil { + return nil, err + } + return &updateSslCheckV2, err + } + return &updateSslCheckV2, nil +} + +func (c Client) UpdateSslCheckV2(id int, SslCheckV2Details *SslCheckV2UpdateInput) (*SslCheckV2Response, *RequestDetails, error) { + body, err := json.Marshal(SslCheckV2Details) + if err != nil { + return nil, nil, err + } + + requestDetails, err := c.makePublicAPICall("PUT", fmt.Sprintf("/tests/ssl/%d", id), bytes.NewBuffer(body), nil) + if err != nil { + return nil, requestDetails, err + } + + updateSslCheckV2, err := parseUpdateSslCheckV2Response(requestDetails.ResponseBody) + if err != nil { + return updateSslCheckV2, requestDetails, err + } + + return updateSslCheckV2, requestDetails, nil +} diff --git a/syntheticsclientv2/update_sslcheckv2_test.go b/syntheticsclientv2/update_sslcheckv2_test.go new file mode 100644 index 0000000..8e4459d --- /dev/null +++ b/syntheticsclientv2/update_sslcheckv2_test.go @@ -0,0 +1,334 @@ +//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" + "io" + "net/http" + "reflect" + "testing" +) + +var ( + updateSslCheckV2Body = `{"test":{"name":"ssl-check-updated","frequency":10,"schedulingStrategy":"concurrent","active":false,"locationIds":["aws-us-east-1"],"customProperties":[{"key":"env","value":"stage"}],"automaticRetries":2,"host":"example.com","port":8443,"serverName":"example.com","allowSelfSigned":false,"allowUntrustedRoot":true,"caCertificateId":42,"validations":[{"name":"Certificate expires later","type":"assert_numeric","actual":"{{certificate.days_until_expiration}}","expected":"15","comparator":"is_greater_than"}]}}` + inputSslCheckV2Update = SslCheckV2UpdateInput{} +) + +func TestUpdateSslCheckV2(t *testing.T) { + setup() + defer teardown() + + expectedCaCertificateID := 42 + expectedValidations := []Validations{ + { + Name: "Certificate expires later", + Type: "assert_numeric", + Actual: "{{certificate.days_until_expiration}}", + Expected: "15", + Comparator: "is_greater_than", + }, + } + expectedCustomProperties := []CustomProperties{ + { + Key: "env", + Value: "stage", + }, + } + + testMux.HandleFunc("/tests/ssl/1650", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + requestBody, err := io.ReadAll(r.Body) + if err != nil { + t.Fatal(err) + } + + var requestEnvelope map[string]json.RawMessage + err = json.Unmarshal(requestBody, &requestEnvelope) + if err != nil { + t.Fatal(err) + } + rawTest, ok := requestEnvelope["test"] + if !ok { + t.Fatal("request body missing test envelope") + } + + var requestTestFields map[string]json.RawMessage + err = json.Unmarshal(rawTest, &requestTestFields) + if err != nil { + t.Fatal(err) + } + for _, field := range []string{"host", "port", "serverName", "allowSelfSigned", "allowUntrustedRoot", "caCertificateId", "validations", "customProperties"} { + if _, ok := requestTestFields[field]; !ok { + t.Errorf("request body missing test.%s", field) + } + } + + requestSslCheckV2Data := SslCheckV2UpdateInput{} + err = json.Unmarshal(requestBody, &requestSslCheckV2Data) + if err != nil { + t.Fatal(err) + } + if requestSslCheckV2Data.Test.Host == nil || !reflect.DeepEqual(*requestSslCheckV2Data.Test.Host, "example.com") { + t.Errorf("request body host \n\n%#v want \n\n%#v", requestSslCheckV2Data.Test.Host, "example.com") + } + if requestSslCheckV2Data.Test.Port == nil || !reflect.DeepEqual(*requestSslCheckV2Data.Test.Port, 8443) { + t.Errorf("request body port \n\n%#v want \n\n%#v", requestSslCheckV2Data.Test.Port, 8443) + } + if requestSslCheckV2Data.Test.ServerName == nil || + requestSslCheckV2Data.Test.ServerName.Value == nil || + !reflect.DeepEqual(*requestSslCheckV2Data.Test.ServerName.Value, "example.com") { + t.Errorf("request body server name \n\n%#v want \n\n%#v", requestSslCheckV2Data.Test.ServerName, "example.com") + } + if requestSslCheckV2Data.Test.AllowSelfSigned == nil || !reflect.DeepEqual(*requestSslCheckV2Data.Test.AllowSelfSigned, false) { + t.Errorf("request body allow self signed \n\n%#v want \n\n%#v", requestSslCheckV2Data.Test.AllowSelfSigned, false) + } + if requestSslCheckV2Data.Test.AllowUntrustedRoot == nil || !reflect.DeepEqual(*requestSslCheckV2Data.Test.AllowUntrustedRoot, true) { + t.Errorf("request body allow untrusted root \n\n%#v want \n\n%#v", requestSslCheckV2Data.Test.AllowUntrustedRoot, true) + } + if requestSslCheckV2Data.Test.CaCertificateID == nil || + requestSslCheckV2Data.Test.CaCertificateID.Value == nil || + !reflect.DeepEqual(*requestSslCheckV2Data.Test.CaCertificateID.Value, expectedCaCertificateID) { + t.Errorf("request body ca certificate id \n\n%#v want \n\n%#v", requestSslCheckV2Data.Test.CaCertificateID, expectedCaCertificateID) + } + if requestSslCheckV2Data.Test.Validations == nil || !reflect.DeepEqual(*requestSslCheckV2Data.Test.Validations, expectedValidations) { + t.Errorf("request body validations \n\n%#v want \n\n%#v", requestSslCheckV2Data.Test.Validations, expectedValidations) + } + if requestSslCheckV2Data.Test.Customproperties == nil || !reflect.DeepEqual(*requestSslCheckV2Data.Test.Customproperties, expectedCustomProperties) { + t.Errorf("request body custom properties \n\n%#v want \n\n%#v", requestSslCheckV2Data.Test.Customproperties, expectedCustomProperties) + } + + _, err = w.Write([]byte(updateSslCheckV2Body)) + 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(err) + } + + if !reflect.DeepEqual(resp.Test.Name, "ssl-check-updated") { + t.Errorf("returned \n\n%#v want \n\n%#v", resp.Test.Name, "ssl-check-updated") + } + if !reflect.DeepEqual(resp.Test.Host, "example.com") { + t.Errorf("returned \n\n%#v want \n\n%#v", resp.Test.Host, "example.com") + } + if !reflect.DeepEqual(resp.Test.Port, 8443) { + t.Errorf("returned \n\n%#v want \n\n%#v", resp.Test.Port, 8443) + } + if resp.Test.CaCertificateID == nil || !reflect.DeepEqual(*resp.Test.CaCertificateID, expectedCaCertificateID) { + t.Errorf("returned \n\n%#v want \n\n%#v", resp.Test.CaCertificateID, expectedCaCertificateID) + } + if !reflect.DeepEqual(resp.Test.Validations, expectedValidations) { + t.Errorf("returned \n\n%#v want \n\n%#v", resp.Test.Validations, expectedValidations) + } +} + +func TestUpdateSslCheckV2AllowsPartialPayload(t *testing.T) { + setup() + defer teardown() + + active := false + inputSslCheckV2Update := SslCheckV2UpdateInput{} + inputSslCheckV2Update.Test.Active = &active + + testMux.HandleFunc("/tests/ssl/1654", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + + requestBody, err := io.ReadAll(r.Body) + if err != nil { + t.Fatal(err) + } + + var requestEnvelope map[string]json.RawMessage + err = json.Unmarshal(requestBody, &requestEnvelope) + if err != nil { + t.Fatal(err) + } + + rawTest, ok := requestEnvelope["test"] + if !ok { + t.Fatal("request body missing test envelope") + } + + var requestTestFields map[string]json.RawMessage + err = json.Unmarshal(rawTest, &requestTestFields) + if err != nil { + t.Fatal(err) + } + + if len(requestTestFields) != 1 { + t.Fatalf("request body test field count %d want 1: %s", len(requestTestFields), requestBody) + } + rawActive, ok := requestTestFields["active"] + if !ok { + t.Fatal("request body missing test.active") + } + if string(rawActive) != "false" { + t.Fatalf("request body test.active %s want false", rawActive) + } + + w.WriteHeader(http.StatusOK) + }) + + resp, _, err := testClient.UpdateSslCheckV2(1654, &inputSslCheckV2Update) + if err != nil { + t.Fatal(err) + } + if resp == nil { + t.Fatal("expected non-nil response for blank successful update body") + } +} + +func TestUpdateSslCheckV2AllowsEmptyValidations(t *testing.T) { + setup() + defer teardown() + + inputSslCheckV2Data := SslCheckV2UpdateInput{} + validations := make([]Validations, 0) + inputSslCheckV2Data.Test.Validations = &validations + + testMux.HandleFunc("/tests/ssl/1651", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + requestBody, err := io.ReadAll(r.Body) + if err != nil { + t.Fatal(err) + } + + var requestEnvelope map[string]json.RawMessage + err = json.Unmarshal(requestBody, &requestEnvelope) + if err != nil { + t.Fatal(err) + } + rawTest, ok := requestEnvelope["test"] + if !ok { + t.Fatal("request body missing test envelope") + } + + var requestTestFields map[string]json.RawMessage + err = json.Unmarshal(rawTest, &requestTestFields) + if err != nil { + t.Fatal(err) + } + rawValidations, ok := requestTestFields["validations"] + if !ok { + t.Fatal("request body missing test.validations") + } + + var validations []json.RawMessage + err = json.Unmarshal(rawValidations, &validations) + if err != nil { + t.Fatal(err) + } + if validations == nil { + t.Fatalf("request body validations \n\n%#v want empty slice", string(rawValidations)) + } + if len(validations) != 0 { + t.Errorf("request body validations length \n\n%#v want \n\n%#v", len(validations), 0) + } + + _, err = w.Write([]byte(updateSslCheckV2Body)) + if err != nil { + t.Fatal(err) + } + }) + + _, _, err := testClient.UpdateSslCheckV2(1651, &inputSslCheckV2Data) + if err != nil { + t.Fatal(err) + } +} + +func TestUpdateSslCheckV2PreservesNilServerName(t *testing.T) { + setup() + defer teardown() + + inputSslCheckV2Data := SslCheckV2UpdateInput{} + inputSslCheckV2Data.Test.ServerName = NewNullString() + + testMux.HandleFunc("/tests/ssl/1653", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + requestBody, err := io.ReadAll(r.Body) + if err != nil { + t.Fatal(err) + } + + var requestEnvelope map[string]json.RawMessage + err = json.Unmarshal(requestBody, &requestEnvelope) + if err != nil { + t.Fatal(err) + } + rawTest, ok := requestEnvelope["test"] + if !ok { + t.Fatal("request body missing test envelope") + } + + var requestTestFields map[string]json.RawMessage + err = json.Unmarshal(rawTest, &requestTestFields) + if err != nil { + t.Fatal(err) + } + rawServerName, ok := requestTestFields["serverName"] + if !ok { + t.Fatal("request body missing test.serverName") + } + if string(rawServerName) != "null" { + t.Fatalf("request body server name \n\n%#v want JSON null", string(rawServerName)) + } + + _, err = w.Write([]byte(updateSslCheckV2Body)) + if err != nil { + t.Fatal(err) + } + }) + + _, _, err := testClient.UpdateSslCheckV2(1653, &inputSslCheckV2Data) + if err != nil { + t.Fatal(err) + } +} + +func TestUpdateSslCheckV2BlankResponse(t *testing.T) { + setup() + defer teardown() + + testMux.HandleFunc("/tests/ssl/1652", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + w.WriteHeader(http.StatusOK) + }) + + err := json.Unmarshal([]byte(updateSslCheckV2Body), &inputSslCheckV2Update) + if err != nil { + t.Fatal(err) + } + + resp, _, err := testClient.UpdateSslCheckV2(1652, &inputSslCheckV2Update) + if err != nil { + t.Fatal(err) + } + if resp == nil { + t.Fatal("expected non-nil response for blank successful update body") + } +}