Giter Club home page Giter Club logo

statuscake-go's People

Contributors

dependabot[bot] avatar tomasbasham avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

Forkers

sharkymcdongles

statuscake-go's Issues

Cannot update BasicUser & BasicPass values

I suspect this is an API issue rather than within the client, but figured this would be the best place to report it for now.

I've been working on writing a custom Terraform provider for StatusCake using this client & the beta api, and it seems that the BasicUser & BasicPass properties for an uptime request cannot be update by the api client:

package main

import (
	"context"
	"github.com/StatusCakeDev/statuscake-go"
	"log"
	"os"
)

func main() {
	apiToken, _ := os.LookupEnv("STATUSCAKE_API_KEY")

	client := statuscake.NewAPIClient(apiToken)

	res, err := client.CreateUptimeTest(context.TODO()).
		Name("my test").
		TestType("HTTP").
		WebsiteURL("https://www.example.com").
		CheckRate(300).
		Execute()

	if err != nil {
		panic(err)
	}

	log.Println("Successfully created Uptime test")

	err = client.UpdateUptimeTest(context.TODO(), res.Data.NewID).
		BasicUser("user").
		BasicPass("pass").
		Execute()

	if err != nil {
		panic(err)
	}

	log.Println("Successfully updated Uptime test")
}
terraform-provider-statuscake2 on  main took 3s
❯ go run te.go
2021/10/03 16:22:55 Successfully created Uptime test
panic: Invalid request. No changes were requested. Please check your inputs and try again.

goroutine 1 [running]:
main.main()
        /c/Users/G-Rath/workspace/projects-personal/terraform-provider-statuscake2/te.go:34 +0x6f7
exit status 2

Unexported fields in API structs lead to problems with API mocking

Is your feature request related to a problem? Please describe.
statuscale-go client has interfaces like UptimeAPI, which is great. It allows us to mock them in our unit tests. The problem is with structs like APICreateUptimeTestRequest or APIUpdateUptimeTestRequest, where fields are private and there are not read-access methods. So we cannot really validate in tests whether properties have valid values. Workaround would be to use httpmock, which raises a question of the necessity of the statuscake-go client in general. In this case we could use REST api.

Describe the solution you'd like
It would be great to have properties exported in structs like APICreateUptimeTestRequest or APIUpdateUptimeTestRequest

Retry is not working for update methods

Describe the bug
StatusCake client supports retry, but it's working only for requests without request body. In the case of requests with a body, client tries to read from an empty (already read) buffer during retries. So following (retried) requests fail because the ContentLength header does not match the body size (body is empty).

To Reproduce
Simple test to reproduce a bug:

package statuscake

import (
	"context"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/StatusCakeDev/statuscake-go"
	"github.com/stretchr/testify/assert"
)

// this works (GET method)
func Test_GetUptimeTest_Retry(t *testing.T) {
	t.Run("Get UptimeTest retry", func(t *testing.T) {
		ts := createMockServer(3)
		defer ts.Close()

		client := statuscake.NewClient(
			statuscake.WithHost(ts.URL),
			statuscake.WithMaxRetries(5),
		)

		ut, err := client.GetUptimeTest(context.Background(), "my-test-id").Execute()
		assert.NoError(t, err, "Expecting no error")
		assert.NotNil(t, ut, "Expecting UptimeTest object")
	})
}

// this does not work (PUT method)
func Test_UpdateUptimeTest_Retry(t *testing.T) {
	t.Run("Update UptimeTest retry", func(t *testing.T) {
		ts := createMockServer(3)
		defer ts.Close()

		client := statuscake.NewClient(
			statuscake.WithHost(ts.URL),
			statuscake.WithMaxRetries(5),
		)

		err := client.UpdateUptimeTest(context.Background(), "my-test-id").Paused(true).Execute()
		assert.NoError(t, err, "Expecting no error")
	})
}

func createMockServer(networkFailures int) *httptest.Server {
	counter := 1
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if counter <= networkFailures { // simulate network issue - close connection
			hj, _ := w.(http.Hijacker)
			conn, _, _ := hj.Hijack()
			_ = conn.Close()
		} else if r.Method == http.MethodGet { // get test
			w.Header().Add("Content-Type", contentType)
			w.WriteHeader(200)
			_, _ = w.Write([]byte(`{"data":{"id":"my-test-id","paused":false,"name":"Google","test_type":"HTTP","website_url":"https://www.google.com","check_rate":900,"confirmation":2,"contact_groups":[],"do_not_find":false,"enable_ssl_alert":false,"follow_redirects":true,"include_header":false,"servers":[],"processing":false,"status":"up","tags":[],"timeout":15,"trigger_rate":0,"uptime":100,"use_jar":false,"last_tested_at":"2022-08-18T06:36:53+00:00","next_location":"UNSET","status_codes":["204"]}}`))
		} else if r.Method == http.MethodPut { // update test
			w.Header().Add("Content-Type", contentType)
			w.WriteHeader(204)
		}
		counter++
	}))
	return ts
}

Expected behavior
Retry should also work for update methods.

Device:

  • OS: macOS12.5
  • GO: 1.18.2

API request failures seem to be able to return empty errors

Describe the bug

  • UpdateUptimeTest request failures seem to be able to return an empty error ("").
  • GetUptimeTest requests seem to be able to return an empty uptime test object with no error.

Additional context
We are running a small go tool to perform OAuth token updates on StatusCake uptime tests.

To Reproduce
We've since found the Debug option within the library but it's too verbose to run all the time. We've built in flags so we can try to get more detail next time if it becomes reproducible/consistent.

Most recently it was Updating an uptime test. We've seen it a fair few times on the GetUptimeTest too, please note the edge case catches in the code below.

You could use the StatusCake API changes that resulted in support request 22508200886017? They triggered it persistently. My hunch is it's a response that doesn't correspond to the expected response format, but that's a complete guess based on similar issues I've seen elsewhere in client libs.

Expected behavior
Library errors should allow clients to provide context failure to StatusCake. An "" error return is probably never be acceptable.

Detail
I've smashed the relevant bits out of a few places into the below representative code:

client, err := NewStatusCakeClient(statusCakeCfg)
if err != nil {
	return "", err
}

testResp, err := client.GetUptimeTest(ctx, id).Execute()
if err != nil {
	return "", fmt.Errorf("failed to get Uptime Test: %w", err)
}

if testResp.Data.Name == "" {
	return "", fmt.Errorf("failed to get Uptime Test: %w", errors.New("response returned without error but has no name"))
}

if testResp.Data.ID == "" {
	return "", fmt.Errorf("failed to get Uptime Test: %w", errors.New("response returned without error but has no ID"))
}

test := testResp.Data
l.Info().Str("name", t.Name).Msgf("Found test: %s", t.Name)
token, err := GetOAuthTokens(ctx, l, oathConfig, refreshToken)
if err != nil {
	return "", fmt.Errorf("failed to get new token: %w", err)
}

if token.AccessToken == "" {
	return token.RefreshToken, errors.New("access token was empty")
}

l.Debug().Str("token", token.AccessToken).Str("refreshtoken", token.RefreshToken).Msgf("Got new token: %s", token.AccessToken)
bytes, err := json.Marshal(&struct {
	Content       string `json:"content-type"`
	Cache         string `json:"cache-control"`
	Authorization string `json:"authorization"`
}{
	Content:       "application/json",
	Cache:         "no-cache",
	Authorization: fmt.Sprintf("Bearer %s", token.AccessToken),
})

header := string(bytes)
l.Debug().Msgf("Got new header state: %s", header)
if err != nil {
	return token.RefreshToken, fmt.Errorf("failed to encode new custom header state: %w", err)
}

r := client.UpdateUptimeTest(ctx, id).CustomHeader(customHeader)
if err := r.Execute(); err != nil {
	return token.RefreshToken, fmt.Errorf("failed to update uptime test: %w", err)
}

return token.RefreshToken, nil

Application logs look like the following, note the lack of error detail, I'd expect an error there that described the issue.

2024-04-17T11:31:29.750+01:00 INF Found test: Product - Prod - API every=600000 id=123456 name="Product - Prod - API"
2024-04-17T11:31:29.891+01:00 ERR Failed to update Uptime Test authentication: failed to update uptime test:  error="failed to update uptime test: " every=600000 id=123456 

Can't unmarshal when using start and end in ListUptimeTestHistory function

Hello guys, I tried to fetch uptime history using start and end method but it gives me error json unmarshal. Here's my code:

fmt.Println(s.Unix(), e.Unix())
	res, err := client.ListUptimeTestHistory(context.Background(), "6128010").
		Start(1632268800).End(1632355140).Limit(100).
		Execute()

	if err != nil {
		panic(err)
	}

	data, _ := json.MarshalIndent(res.Data, "", "🐱")

	fmt.Print(string(data))

And here is the error:

panic: json: cannot unmarshal array into Go struct field UptimeTestHistory.data of type map[string]statuscake.UptimeTestHistoryResult: failed to deserialise response body

I don't know if it's not the way I should use the method or it's bug on the code itself. I don't find any example with Start and End method both in documentation and github. I'm using Go 1.17 on darwin.

UpdateUptimeTest calls don't seem to work

Thanks for the effort and making this available, much appreciated!

I've got an OAuth2 API that I need to monitor so I'm just slapping something together to keep the custom header token refreshed. I've been successfully using this library for the other tasks (checking the Uptime Test exists, getting a list of Uptime Tests etc) but have run into issues with the UpdateUptimeTest call. This is the first application/x-www-form-urlencoded call I've needed to make from your library.

Calling the UpdateUptimeTest library function seems to 500 and error (below) even when doing it by "hand" with the same configuration works correctly.

2022-10-25T20:43:33.113+01:00 FTL Failed to update Uptime Test authentication: failed to update uptime test: Unexpected error. Try again later. error="failed to update uptime test: Unexpected error. Try again later."

I've made the call successfully in order to confirm that the issue is not with the API. Below is a chunk of that code, the uncommented block is just calling the API using a http.Client, the lower commented out section is the library call to do the same thing (which I can't get to work, error above).

type StatusCakeClient struct {
	c   *statuscake.Client
	h   *http.Client
	cfg *config.StatusCake
}

// UpdateUptimeTestCustomHeader replaces the custom header of the supplied Uptime Test.
func (s *StatusCakeClient) UpdateUptimeTestCustomHeader(ctx context.Context, id, customHeader string) (err error) {
	requestBody := url.Values{}
	requestBody.Add("custom_header", customHeader)
	req, err := http.NewRequestWithContext(ctx, http.MethodPut, fmt.Sprintf("https://api.statuscake.com/v1/uptime/%v", id), strings.NewReader(requestBody.Encode()))
	if err != nil {
		return fmt.Errorf("failed to construct Uptime Test update request: %w", err)
	}

	req.Header.Set("content-type", "application/x-www-form-urlencoded")
	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", s.cfg.Token))
	resp, err := s.h.Do(req)
	defer func() {
		if resp != nil && resp.Body != nil {
			if closeErr := resp.Body.Close(); closeErr != nil && err == nil {
				err = fmt.Errorf("failed to close response body: %w", closeErr)
			}
		}
	}()

	if err != nil {
		return fmt.Errorf("failed to send Uptime Test update request: %w", err)
	}

	if resp.StatusCode != 204 {
		bytes, err := io.ReadAll(resp.Body)
		if err != nil {
			return fmt.Errorf("%w (%v) and couldn't read body: %v", ErrNot20XResponse, resp.StatusCode, err)
		}

		return fmt.Errorf("%w (%v): %s", ErrNot20XResponse, resp.StatusCode, string(bytes))
	}

	return nil

	// Library call doesn't seem to work, 500s?
	//
	// r := s.c.UpdateUptimeTest(ctx, id)
	// r.CustomHeader(customHeader)
	// if err := r.Execute(); err != nil {
	// 	return fmt.Errorf("failed to update uptime test: %w", err)
	// }
	//
	// return nil
}

Am I calling this wrong from within the library?

"include_header" value is not included in uptime test response

It seems that the include_header property is not included in the "get single uptime test" response, which means it's not possible to check what value it is set to.

This makes it awkward to handle in e.g. a terraform provider, because we either have to mark the attribute as requiring a completely new resource to be created, or just hope what is in our state is correct (and maybe always include it when updating that resource) which means we can't detect drift.

Cannot set "status_codes" to empty array when creating an uptime test

If you create a new uptime test with an empty array for status_codes, the api instead adds all the status codes.

This means you can only remove all statuscodes with an update request.

It would be good if there way a way to tell the api to not create an uptime test with any statuscodes.

The result of the last retried call is ignored

Describe the bug
The behaviour of the retry feature is a little bit weird. The result of the last retried call is ignored and the error is returned.

To Reproduce
A simple test to reproduce:

package statuscake

import (
	"context"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/StatusCakeDev/statuscake-go"
	"github.com/stretchr/testify/assert"
)

func Test_ListUptimeTests_Retry(t *testing.T) {
	t.Run("Test ListUptime retry", func(t *testing.T) {
		serverCallCounter := 0
		ts := createMockServer(1, &serverCallCounter) // first call will fail, but the second will be successful
		defer ts.Close()

		client := statuscake.NewClient(
			statuscake.WithHost(ts.URL),
			statuscake.WithMaxRetries(1), // we expect one (failed) call + one (success) retry in test
		)

		_, err := client.ListUptimeTests(context.Background()).Execute()
		assert.Equal(t, 2, serverCallCounter)        // client called API twice, that's OK
		assert.NoError(t, err, "Expecting no error") // test should pass, retry was successful, but error is returned :sad-panda:
	})
}

func createMockServer(networkFailures int, counter *int) *httptest.Server {
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		*counter++
		if *counter <= networkFailures { // simulate network issue - close connection
			hj, _ := w.(http.Hijacker)
			conn, _, _ := hj.Hijack()
			_ = conn.Close()
		} else if r.Method == http.MethodGet { // get test
			w.Header().Add("Content-Type", "application/json")
			w.WriteHeader(200)
			_, _ = w.Write([]byte(`{"data":[],"metadata":{"page":1,"per_page":25,"page_count":1,"total_count":0}}`))
		}
	}))
	return ts
}

Expected behavior

  1. When I set "MaxRetries=3" I expect the StatusCake client to do 1 regular call + 3 retries.
    • now this works as I expect
  2. When I set "MaxRetries=3", client do 1 regular call + 3 retries. If the call results are fail+fail+fail+success then I expect the StatusCake client's result is a success.
    • this is not working, the last call result is ignored. Note: I think the condition should be moved to line 195.

Device:

  • OS: macOS12.5
  • GO: 1.18.2

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.