Skip to content
This repository has been archived by the owner on Nov 5, 2022. It is now read-only.

Commit

Permalink
Merge pull request #20 from Fallenstedt/0.3.2
Browse files Browse the repository at this point in the history
0.3.2
  • Loading branch information
Fallenstedt authored Jun 19, 2021
2 parents 7d90444 + 39bfcf0 commit c11c3e7
Show file tree
Hide file tree
Showing 17 changed files with 366 additions and 233 deletions.
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.3.1
0.3.2
2 changes: 1 addition & 1 deletion example/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ module github.com/fallenstedt/twitter-stream/example

replace github.com/fallenstedt/twitter-stream => /Users/alex/Projects/twitter-stream/

go 1.15
go 1.16

require github.com/fallenstedt/twitter-stream v0.2.1
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module github.com/fallenstedt/twitter-stream

go 1.15
go 1.16
159 changes: 0 additions & 159 deletions http_client.go

This file was deleted.

36 changes: 0 additions & 36 deletions http_client_mock.go

This file was deleted.

36 changes: 36 additions & 0 deletions httpclient/http_client_mock.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package httpclient

import "net/http"

type mockHttpClient struct {
token string
MockNewHttpRequest func(opts *RequestOpts) (*http.Response, error)
MockGetSearchStream func(queryParams string) (*http.Response, error)
MockGetRules func() (*http.Response, error)
MockAddRules func(queryParams string, body string) (*http.Response, error)
MockGenerateUrl func(name string, queryParams string) (string, error)
}

func NewHttpClientMock(token string) *mockHttpClient {
return &mockHttpClient{token: token}
}

func (t *mockHttpClient) GenerateUrl(name string, queryParams string) (string, error) {
return t.MockGenerateUrl(name, queryParams)
}

func (t *mockHttpClient) GetRules() (*http.Response, error) {
return t.MockGetRules()
}

func (t *mockHttpClient) AddRules(queryParams string, body string) (*http.Response, error) {
return t.MockAddRules(queryParams, body)
}

func (t *mockHttpClient) GetSearchStream(queryParams string) (*http.Response, error) {
return t.MockGetSearchStream(queryParams)
}

func (t *mockHttpClient) NewHttpRequest(opts *RequestOpts) (*http.Response, error) {
return t.MockNewHttpRequest(opts)
}
55 changes: 55 additions & 0 deletions httpclient/http_response_parser.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package httpclient

import (
"errors"
"fmt"
"io/ioutil"
"log"
"math"
"net/http"
"time"
)

// httpResponseParser is a struct that will retry network requests if the response has a status code of 429.
type httpResponseParser struct{}

func (h httpResponseParser) handleResponse(resp *http.Response, opts *RequestOpts, fn func(opts *RequestOpts) (*http.Response, error)) (*http.Response, error) {
// Retry with backoff if 429
if resp.StatusCode == 429 {
log.Printf("Retrying network request %s with backoff", opts.Url)

delay := h.getBackOffTime(opts.Retries)
log.Printf("Sleeping for %v seconds", delay)
time.Sleep(delay)

opts.Retries += 1

return fn(opts)
}

// Reject if 400 or greater
if resp.StatusCode >= 400 {
log.Printf("Network Request at %s failed: %v", opts.Url, resp.StatusCode)

var msg string
if resp.Body != nil {
body, _ := ioutil.ReadAll(resp.Body)
msg = "Network request failed: " + string(body)
} else {
msg = "Network request failed with status" + fmt.Sprint(resp.StatusCode)
}

return nil, errors.New(msg)
}

return resp, nil
}

func (h httpResponseParser) getBackOffTime(retries uint8) time.Duration {
exponentialBackoffCeilingSecs := 30
delaySecs := int(math.Floor((math.Pow(2, float64(retries)) - 1) * 0.5))
if delaySecs > exponentialBackoffCeilingSecs {
delaySecs = 30
}
return time.Duration(delaySecs) * time.Second
}
70 changes: 70 additions & 0 deletions httpclient/http_response_parser_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package httpclient

import (
"net/http"
"testing"
)

func givenHttpResponseParserInstance() *httpResponseParser {
return new(httpResponseParser)
}

func givenFakeHttpResponse(statusCode int) *http.Response {
res := new(http.Response)
res.StatusCode = statusCode
return res
}

func TestHandleResponseShouldReturnIf200(t *testing.T) {
instance := givenHttpResponseParserInstance()
opts := new(RequestOpts)
resp := givenFakeHttpResponse(200)

result, err := instance.handleResponse(resp, opts, func(o *RequestOpts) (*http.Response, error) {
return nil, nil
})

if err != nil {
t.Errorf("Expected not error, got %v", err)
}

if result.StatusCode != 200 {
t.Errorf("Expected a status code of 200")
}
}

func TestHandleResponseShouldRetryRequestIf429(t *testing.T) {
instance := givenHttpResponseParserInstance()
opts := new(RequestOpts)
resp := givenFakeHttpResponse(429)

result, err := instance.handleResponse(resp, opts, func(o *RequestOpts) (*http.Response, error) {
return givenFakeHttpResponse(200), nil
})

if opts.Retries != 1 {
t.Errorf("Expected atleast on retry attempt, got %v", opts.Retries)
}

if err != nil {
t.Errorf("Expected not error, got %v", err)
}

if result.StatusCode != 200 {
t.Errorf("Expected a status code of 200")
}
}

func TestHandleResponseShouldRejectIf400OrHigher(t *testing.T) {
instance := givenHttpResponseParserInstance()
opts := new(RequestOpts)
resp := givenFakeHttpResponse(401)

_, err := instance.handleResponse(resp, opts, func(o *RequestOpts) (*http.Response, error) {
return nil, nil
})

if err == nil {
t.Errorf("Expected error, got nil")
}
}
Loading

0 comments on commit c11c3e7

Please sign in to comment.