Skip to content

Commit

Permalink
feat: add rate limit and credits info to existing commands (jsdelivr#114
Browse files Browse the repository at this point in the history
)

Co-authored-by: Martin Kolárik <[email protected]>
  • Loading branch information
radulucut and MartinKolarik authored Jul 10, 2024
1 parent 22011f6 commit 963e544
Show file tree
Hide file tree
Showing 32 changed files with 634 additions and 295 deletions.
14 changes: 14 additions & 0 deletions cmd/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"io"
"io/fs"
"net"
"net/http"
"os"
"path/filepath"
"runtime"
Expand Down Expand Up @@ -303,3 +304,16 @@ func getMeasurementsPath() string {
func getHistoryPath() string {
return filepath.Join(getSessionPath(), "history")
}

func silenceUsageOnCreateMeasurementError(err error) bool {
e, ok := err.(*globalping.MeasurementError)
if ok {
switch e.Code {
case http.StatusBadRequest, http.StatusUnprocessableEntity:
return false
default:
return true
}
}
return true
}
6 changes: 2 additions & 4 deletions cmd/dns.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,11 +99,9 @@ func (r *Root) RunDNS(cmd *cobra.Command, args []string) error {
opts.Options.IPVersion = globalping.IPVersion6
}

res, showHelp, err := r.client.CreateMeasurement(opts)
res, err := r.client.CreateMeasurement(opts)
if err != nil {
if !showHelp {
cmd.SilenceUsage = true
}
cmd.SilenceUsage = silenceUsageOnCreateMeasurementError(err)
return err
}

Expand Down
6 changes: 3 additions & 3 deletions cmd/dns_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func Test_Execute_DNS_Default(t *testing.T) {
expectedResponse := createDefaultMeasurementCreateResponse()

gbMock := mocks.NewMockClient(ctrl)
gbMock.EXPECT().CreateMeasurement(expectedOpts).Times(1).Return(expectedResponse, false, nil)
gbMock.EXPECT().CreateMeasurement(expectedOpts).Times(1).Return(expectedResponse, nil)

viewerMock := mocks.NewMockViewer(ctrl)
viewerMock.EXPECT().Output(measurementID1, expectedOpts).Times(1).Return(nil)
Expand Down Expand Up @@ -96,7 +96,7 @@ func Test_Execute_DNS_IPv4(t *testing.T) {
expectedResponse := createDefaultMeasurementCreateResponse()

gbMock := mocks.NewMockClient(ctrl)
gbMock.EXPECT().CreateMeasurement(expectedOpts).Times(1).Return(expectedResponse, false, nil)
gbMock.EXPECT().CreateMeasurement(expectedOpts).Times(1).Return(expectedResponse, nil)

viewerMock := mocks.NewMockViewer(ctrl)
viewerMock.EXPECT().Output(measurementID1, expectedOpts).Times(1).Return(nil)
Expand Down Expand Up @@ -136,7 +136,7 @@ func Test_Execute_DNS_IPv6(t *testing.T) {
expectedResponse := createDefaultMeasurementCreateResponse()

gbMock := mocks.NewMockClient(ctrl)
gbMock.EXPECT().CreateMeasurement(expectedOpts).Times(1).Return(expectedResponse, false, nil)
gbMock.EXPECT().CreateMeasurement(expectedOpts).Times(1).Return(expectedResponse, nil)

viewerMock := mocks.NewMockViewer(ctrl)
viewerMock.EXPECT().Output(measurementID1, expectedOpts).Times(1).Return(nil)
Expand Down
6 changes: 2 additions & 4 deletions cmd/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,11 +108,9 @@ func (r *Root) RunHTTP(cmd *cobra.Command, args []string) error {
opts.Options.IPVersion = globalping.IPVersion6
}

res, showHelp, err := r.client.CreateMeasurement(opts)
res, err := r.client.CreateMeasurement(opts)
if err != nil {
if !showHelp {
cmd.SilenceUsage = true
}
cmd.SilenceUsage = silenceUsageOnCreateMeasurementError(err)
return err
}

Expand Down
6 changes: 3 additions & 3 deletions cmd/http_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ func Test_Execute_HTTP_Default(t *testing.T) {
expectedResponse := createDefaultMeasurementCreateResponse()

gbMock := mocks.NewMockClient(ctrl)
gbMock.EXPECT().CreateMeasurement(expectedOpts).Times(1).Return(expectedResponse, false, nil)
gbMock.EXPECT().CreateMeasurement(expectedOpts).Times(1).Return(expectedResponse, nil)

viewerMock := mocks.NewMockViewer(ctrl)
viewerMock.EXPECT().Output(measurementID1, expectedOpts).Times(1).Return(nil)
Expand Down Expand Up @@ -108,7 +108,7 @@ func Test_Execute_HTTP_IPv4(t *testing.T) {
expectedResponse := createDefaultMeasurementCreateResponse()

gbMock := mocks.NewMockClient(ctrl)
gbMock.EXPECT().CreateMeasurement(expectedOpts).Times(1).Return(expectedResponse, false, nil)
gbMock.EXPECT().CreateMeasurement(expectedOpts).Times(1).Return(expectedResponse, nil)

viewerMock := mocks.NewMockViewer(ctrl)
viewerMock.EXPECT().Output(measurementID1, expectedOpts).Times(1).Return(nil)
Expand Down Expand Up @@ -152,7 +152,7 @@ func Test_Execute_HTTP_IPv6(t *testing.T) {
expectedResponse := createDefaultMeasurementCreateResponse()

gbMock := mocks.NewMockClient(ctrl)
gbMock.EXPECT().CreateMeasurement(expectedOpts).Times(1).Return(expectedResponse, false, nil)
gbMock.EXPECT().CreateMeasurement(expectedOpts).Times(1).Return(expectedResponse, nil)

viewerMock := mocks.NewMockViewer(ctrl)
viewerMock.EXPECT().Output(measurementID1, expectedOpts).Times(1).Return(nil)
Expand Down
7 changes: 4 additions & 3 deletions cmd/install_probe_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,10 @@ Please confirm to pull and run our Docker container (ghcr.io/jsdelivr/globalping
`, w.String())

expectedCtx := &view.Context{
History: view.NewHistoryBuffer(1),
From: "world",
Limit: 1,
History: view.NewHistoryBuffer(1),
From: "world",
Limit: 1,
RunSessionStartedAt: defaultCurrentTime,
}
assert.Equal(t, expectedCtx, ctx)
}
6 changes: 2 additions & 4 deletions cmd/mtr.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,11 +90,9 @@ func (r *Root) RunMTR(cmd *cobra.Command, args []string) error {
opts.Options.IPVersion = globalping.IPVersion6
}

res, showHelp, err := r.client.CreateMeasurement(opts)
res, err := r.client.CreateMeasurement(opts)
if err != nil {
if !showHelp {
cmd.SilenceUsage = true
}
cmd.SilenceUsage = silenceUsageOnCreateMeasurementError(err)
return err
}

Expand Down
6 changes: 3 additions & 3 deletions cmd/mtr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ func Test_Execute_MTR_Default(t *testing.T) {
expectedResponse := createDefaultMeasurementCreateResponse()

gbMock := mocks.NewMockClient(ctrl)
gbMock.EXPECT().CreateMeasurement(expectedOpts).Times(1).Return(expectedResponse, false, nil)
gbMock.EXPECT().CreateMeasurement(expectedOpts).Times(1).Return(expectedResponse, nil)

viewerMock := mocks.NewMockViewer(ctrl)
viewerMock.EXPECT().Output(measurementID1, expectedOpts).Times(1).Return(nil)
Expand Down Expand Up @@ -87,7 +87,7 @@ func Test_Execute_MTR_IPv4(t *testing.T) {
expectedResponse := createDefaultMeasurementCreateResponse()

gbMock := mocks.NewMockClient(ctrl)
gbMock.EXPECT().CreateMeasurement(expectedOpts).Times(1).Return(expectedResponse, false, nil)
gbMock.EXPECT().CreateMeasurement(expectedOpts).Times(1).Return(expectedResponse, nil)

viewerMock := mocks.NewMockViewer(ctrl)
viewerMock.EXPECT().Output(measurementID1, expectedOpts).Times(1).Return(nil)
Expand Down Expand Up @@ -126,7 +126,7 @@ func Test_Execute_MTR_IPv6(t *testing.T) {
expectedResponse := createDefaultMeasurementCreateResponse()

gbMock := mocks.NewMockClient(ctrl)
gbMock.EXPECT().CreateMeasurement(expectedOpts).Times(1).Return(expectedResponse, false, nil)
gbMock.EXPECT().CreateMeasurement(expectedOpts).Times(1).Return(expectedResponse, nil)

viewerMock := mocks.NewMockViewer(ctrl)
viewerMock.EXPECT().Output(measurementID1, expectedOpts).Times(1).Return(nil)
Expand Down
25 changes: 18 additions & 7 deletions cmd/ping.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package cmd

import (
"fmt"
"net/http"
"syscall"
"time"

Expand Down Expand Up @@ -117,15 +118,26 @@ func (r *Root) pingInfinite(opts *globalping.MeasurementCreate) error {
}()
<-r.cancel

if err == nil {
r.viewer.OutputSummary()
r.viewer.OutputSummary()
if err != nil && r.ctx.MeasurementsCreated > 0 {
e, ok := err.(*globalping.MeasurementError)
if ok && e.Code == http.StatusTooManyRequests {
r.Cmd.SilenceErrors = true
if r.ctx.CIMode {
r.printer.Printf("> %s\n", e.Message)
} else {
r.printer.Printf(r.printer.Color("> "+e.Message, view.ColorLightYellow) + "\n")
}
}
}
r.viewer.OutputShare()
return err
}

func (r *Root) ping(opts *globalping.MeasurementCreate) error {
var runErr error
mbuf := NewMeasurementsBuffer(10) // 10 is the maximum number of measurements that can be in progress at the same time
r.ctx.RunSessionStartedAt = r.time.Now()
for {
mbuf.Restart()
elapsedTime := time.Duration(0)
Expand Down Expand Up @@ -160,8 +172,9 @@ func (r *Root) ping(opts *globalping.MeasurementCreate) error {
hm, err := r.createMeasurement(opts)
if err != nil {
runErr = err // Return the error after all measurements have finished
} else {
mbuf.Append(hm)
}
mbuf.Append(hm)
elapsedTime += r.time.Now().Sub(start)
}
el = mbuf.Next()
Expand All @@ -186,11 +199,9 @@ func (r *Root) ping(opts *globalping.MeasurementCreate) error {
}

func (r *Root) createMeasurement(opts *globalping.MeasurementCreate) (*view.HistoryItem, error) {
res, showHelp, err := r.client.CreateMeasurement(opts)
res, err := r.client.CreateMeasurement(opts)
if err != nil {
if !showHelp {
r.Cmd.SilenceUsage = true
}
r.Cmd.SilenceUsage = silenceUsageOnCreateMeasurementError(err)
return nil, err
}
r.ctx.MeasurementsCreated++
Expand Down
89 changes: 79 additions & 10 deletions cmd/ping_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ func Test_Execute_Ping_Default(t *testing.T) {
expectedResponse := createDefaultMeasurementCreateResponse()

gbMock := mocks.NewMockClient(ctrl)
gbMock.EXPECT().CreateMeasurement(expectedOpts).Times(1).Return(expectedResponse, false, nil)
gbMock.EXPECT().CreateMeasurement(expectedOpts).Times(1).Return(expectedResponse, nil)

viewerMock := mocks.NewMockViewer(ctrl)
viewerMock.EXPECT().Output(measurementID1, expectedOpts).Times(1).Return(nil)
Expand Down Expand Up @@ -77,7 +77,7 @@ func Test_Execute_Ping_Locations_And_Session(t *testing.T) {

totalCalls := 10
gbMock := mocks.NewMockClient(ctrl)
gbMock.EXPECT().CreateMeasurement(expectedOpts).Times(totalCalls).Return(expectedResponse, false, nil)
gbMock.EXPECT().CreateMeasurement(expectedOpts).Times(totalCalls).Return(expectedResponse, nil)

viewerMock := mocks.NewMockViewer(ctrl)
c1 := viewerMock.EXPECT().Output(measurementID1, expectedOpts).Times(4).Return(nil)
Expand Down Expand Up @@ -294,10 +294,10 @@ func Test_Execute_Ping_Infinite(t *testing.T) {
expectedResponse4.ID = measurementID4

gbMock := mocks.NewMockClient(ctrl)
createCall1 := gbMock.EXPECT().CreateMeasurement(expectedOpts1).Return(expectedResponse1, false, nil)
createCall2 := gbMock.EXPECT().CreateMeasurement(expectedOpts2).Return(expectedResponse2, false, nil).After(createCall1)
createCall3 := gbMock.EXPECT().CreateMeasurement(expectedOpts3).Return(expectedResponse3, false, nil).After(createCall2)
gbMock.EXPECT().CreateMeasurement(expectedOpts4).Return(expectedResponse4, false, nil).After(createCall3)
createCall1 := gbMock.EXPECT().CreateMeasurement(expectedOpts1).Return(expectedResponse1, nil)
createCall2 := gbMock.EXPECT().CreateMeasurement(expectedOpts2).Return(expectedResponse2, nil).After(createCall1)
createCall3 := gbMock.EXPECT().CreateMeasurement(expectedOpts3).Return(expectedResponse3, nil).After(createCall2)
gbMock.EXPECT().CreateMeasurement(expectedOpts4).Return(expectedResponse4, nil).After(createCall3)

expectedMeasurement1 := createDefaultMeasurement_MultipleProbes("ping", globalping.StatusFinished)
expectedMeasurement2 := createDefaultMeasurement_MultipleProbes("ping", globalping.StatusInProgress)
Expand Down Expand Up @@ -332,6 +332,7 @@ func Test_Execute_Ping_Infinite(t *testing.T) {
}).After(outputCall6)

viewerMock.EXPECT().OutputSummary().Times(1)
viewerMock.EXPECT().OutputShare().Times(1)

timeMock := mocks.NewMockTime(ctrl)
timeMock.EXPECT().Now().Return(defaultCurrentTime).AnyTimes()
Expand Down Expand Up @@ -364,6 +365,7 @@ func Test_Execute_Ping_Infinite(t *testing.T) {
Infinite: true,
CIMode: true,
MeasurementsCreated: 4,
RunSessionStartedAt: defaultCurrentTime,
}
expectedCtx.History = &view.HistoryBuffer{
Index: 4,
Expand Down Expand Up @@ -435,14 +437,15 @@ func Test_Execute_Ping_Infinite_Output_Error(t *testing.T) {
expectedResponse1 := createDefaultMeasurementCreateResponse()

gbMock := mocks.NewMockClient(ctrl)
gbMock.EXPECT().CreateMeasurement(expectedOpts1).Return(expectedResponse1, false, nil)
gbMock.EXPECT().CreateMeasurement(expectedOpts1).Return(expectedResponse1, nil)

expectedMeasurement := createDefaultMeasurement("ping")
gbMock.EXPECT().GetMeasurement(measurementID1).Return(expectedMeasurement, nil)

viewerMock := mocks.NewMockViewer(ctrl)
viewerMock.EXPECT().OutputInfinite(expectedMeasurement).Return(errors.New("error message"))
viewerMock.EXPECT().OutputSummary().Times(0)
viewerMock.EXPECT().OutputSummary().Times(1)
viewerMock.EXPECT().OutputShare().Times(1)

timeMock := mocks.NewMockTime(ctrl)
timeMock.EXPECT().Now().Return(defaultCurrentTime).AnyTimes()
Expand Down Expand Up @@ -478,6 +481,72 @@ func Test_Execute_Ping_Infinite_Output_Error(t *testing.T) {
assert.Equal(t, expectedHistory, string(b))
}

func Test_Execute_Ping_Infinite_Output_TooManyRequests_Error(t *testing.T) {
t.Cleanup(sessionCleanup)

ctrl := gomock.NewController(t)
defer ctrl.Finish()

expectedOpts1 := createDefaultMeasurementCreate("ping")
expectedOpts1.Options.Packets = 16
expectedOpts2 := createDefaultMeasurementCreate("ping")
expectedOpts2.Options.Packets = 16
expectedOpts2.Locations[0].Magic = measurementID1

expectedResponse1 := createDefaultMeasurementCreateResponse()

gbMock := mocks.NewMockClient(ctrl)
createCall1 := gbMock.EXPECT().CreateMeasurement(expectedOpts1).Return(expectedResponse1, nil)
gbMock.EXPECT().CreateMeasurement(expectedOpts2).Return(nil, &globalping.MeasurementError{
Code: 429,
Message: "too many requests",
}).After(createCall1)

expectedMeasurement := createDefaultMeasurement("ping")
gbMock.EXPECT().GetMeasurement(measurementID1).Return(expectedMeasurement, nil)

viewerMock := mocks.NewMockViewer(ctrl)
waitFn := func(m *globalping.Measurement) error { time.Sleep(5 * time.Millisecond); return nil }
viewerMock.EXPECT().OutputInfinite(expectedMeasurement).DoAndReturn(waitFn)

viewerMock.EXPECT().OutputSummary().Times(1)
viewerMock.EXPECT().OutputShare().Times(1)

timeMock := mocks.NewMockTime(ctrl)
timeMock.EXPECT().Now().Return(defaultCurrentTime).AnyTimes()

w := new(bytes.Buffer)
printer := view.NewPrinter(nil, w, w)
ctx := createDefaultContext("ping")
root := NewRoot(printer, ctx, viewerMock, timeMock, gbMock, nil)
os.Args = []string{"globalping", "ping", "jsdelivr.com", "from", "Berlin", "--infinite", "--share"}
err := root.Cmd.ExecuteContext(context.TODO())
assert.Equal(t, "too many requests", err.Error())

assert.Equal(t, "> too many requests\n", w.String())

expectedCtx := createDefaultExpectedContext("ping")
expectedCtx.History.Find(measurementID1).Status = globalping.StatusFinished
expectedCtx.Packets = 16
expectedCtx.Infinite = true
expectedCtx.Share = true
assert.Equal(t, expectedCtx, ctx)

b, err := os.ReadFile(getMeasurementsPath())
assert.NoError(t, err)
expectedHistory := measurementID1 + "\n"
assert.Equal(t, expectedHistory, string(b))

b, err = os.ReadFile(getHistoryPath())
assert.NoError(t, err)
expectedHistory = createDefaultExpectedHistoryLogItem(
"1",
measurementID1,
"ping jsdelivr.com from Berlin --infinite --share",
)
assert.Equal(t, expectedHistory, string(b))
}

func Test_Execute_Ping_IPv4(t *testing.T) {
t.Cleanup(sessionCleanup)

Expand All @@ -490,7 +559,7 @@ func Test_Execute_Ping_IPv4(t *testing.T) {
expectedResponse := createDefaultMeasurementCreateResponse()

gbMock := mocks.NewMockClient(ctrl)
gbMock.EXPECT().CreateMeasurement(expectedOpts).Times(1).Return(expectedResponse, false, nil)
gbMock.EXPECT().CreateMeasurement(expectedOpts).Times(1).Return(expectedResponse, nil)

viewerMock := mocks.NewMockViewer(ctrl)
viewerMock.EXPECT().Output(measurementID1, expectedOpts).Times(1).Return(nil)
Expand Down Expand Up @@ -527,7 +596,7 @@ func Test_Execute_Ping_IPv6(t *testing.T) {
expectedResponse := createDefaultMeasurementCreateResponse()

gbMock := mocks.NewMockClient(ctrl)
gbMock.EXPECT().CreateMeasurement(expectedOpts).Times(1).Return(expectedResponse, false, nil)
gbMock.EXPECT().CreateMeasurement(expectedOpts).Times(1).Return(expectedResponse, nil)

viewerMock := mocks.NewMockViewer(ctrl)
viewerMock.EXPECT().Output(measurementID1, expectedOpts).Times(1).Return(nil)
Expand Down
Loading

0 comments on commit 963e544

Please sign in to comment.