Skip to content

Commit

Permalink
FEAT: idiomatic checks battery, remove httpmock (#38)
Browse files Browse the repository at this point in the history
* FEAT: idiomatic checks battery, remove httpmock

* add test for html size contentlength shortcut
  • Loading branch information
kynrai committed Jun 10, 2024
1 parent c848cd6 commit 327e8a9
Show file tree
Hide file tree
Showing 14 changed files with 330 additions and 250 deletions.
91 changes: 91 additions & 0 deletions checks/carbon.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package checks

import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"strconv"
)

type CarbonData struct {
Statistics struct {
AdjustedBytes float64 `json:"adjustedBytes"`
Energy float64 `json:"energy"`
Co2 struct {
Grid struct {
Grams float64 `json:"grams"`
Litres float64 `json:"litres"`
} `json:"grid"`
Renewable struct {
Grams float64 `json:"grams"`
Litres float64 `json:"litres"`
} `json:"renewable"`
} `json:"co2"`
} `json:"statistics"`
CleanerThan int `json:"cleanerThan"`
ScanUrl string `json:"scanUrl"`
}

type Carbon struct {
client *http.Client
}

func NewCarbon(client *http.Client) *Carbon {
return &Carbon{client: client}
}

// HtmlSize gets the HTML size of the website
func (c *Carbon) HtmlSize(ctx context.Context, url string) (int, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return 0, err
}
resp, err := c.client.Do(req)
if err != nil {
return 0, fmt.Errorf("failed to get HTML size: %w", err)
}
defer resp.Body.Close()
// short cut to avoid reading body into RAM
if resp.ContentLength != -1 {
return int(resp.ContentLength), nil
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return 0, fmt.Errorf("failed to read response body: %w", err)
}
return len(body), nil
}

// CarbonData gets the carbon data based on the HTML size
func (c *Carbon) CarbonData(ctx context.Context, sizeInBytes int) (*CarbonData, error) {
const carbonDataUrl = "https://api.websitecarbon.com/data"

req, err := http.NewRequestWithContext(ctx, http.MethodGet, carbonDataUrl, nil)
if err != nil {
return nil, err
}
q := req.URL.Query()
q.Add("bytes", strconv.Itoa(sizeInBytes))
q.Add("green", "0")
req.URL.RawQuery = q.Encode()

resp, err := c.client.Do(req)
if err != nil {
return nil, fmt.Errorf("failed to get carbon data: %w", err)
}
defer resp.Body.Close()

body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read response body: %w", err)
}

var carbonData CarbonData
if err := json.Unmarshal(body, &carbonData); err != nil {
return nil, fmt.Errorf("failed to unmarshal carbon data: %w", err)
}

return &carbonData, nil
}
21 changes: 21 additions & 0 deletions checks/carbon_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package checks

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

"github.com/stretchr/testify/assert"
"github.com/xray-web/web-check-api/testutils"
)

func TestCarbonHtmlSize(t *testing.T) {
t.Parallel()
const htmlBody = `<html><body>Test</body></html>`
var size = len(htmlBody)
client := testutils.MockClient(testutils.Response(http.StatusOK, []byte(htmlBody)))
c := NewCarbon(client)
size, err := c.HtmlSize(context.TODO(), "/carbon")
assert.NoError(t, err)
assert.Equal(t, len(htmlBody), size)
}
21 changes: 21 additions & 0 deletions checks/checks.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package checks

import (
"net/http"
"time"
)

type Checks struct {
Carbon *Carbon
Rank *Rank
}

func NewChecks() *Checks {
client := &http.Client{
Timeout: 5 * time.Second,
}
return &Checks{
Carbon: NewCarbon(client),
Rank: NewRank(client),
}
}
40 changes: 40 additions & 0 deletions checks/rank.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package checks

import (
"context"
"encoding/json"
"fmt"
"net/http"
)

type TrancoRanks struct {
Ranks []TrancoRank `json:"ranks"`
}

type TrancoRank struct {
Date string `json:"date"`
Rank int `json:"rank"`
}

type Rank struct {
client *http.Client
}

func NewRank(client *http.Client) *Rank {
return &Rank{client: client}
}

func (r *Rank) GetRank(ctx context.Context, url string) (*TrancoRanks, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("https://tranco-list.eu/api/ranks/domain/%s", url), nil)
if err != nil {
return nil, err
}
resp, err := r.client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()

var result TrancoRanks
return &result, json.NewDecoder(resp.Body).Decode(&result)
}
1 change: 0 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ require (
require (
github.com/PuerkitoBio/goquery v1.9.2
github.com/aeden/traceroute v0.0.0-20210211061815-03f5f7cb7908
github.com/jarcoal/httpmock v1.3.1
github.com/stretchr/testify v1.9.0
golang.org/x/net v0.25.0
golang.org/x/sys v0.20.0 // indirect
Expand Down
4 changes: 0 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@ github.com/gobwas/ws v1.4.0 h1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs=
github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakrc=
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
github.com/jarcoal/httpmock v1.3.1 h1:iUx3whfZWVf3jT01hQTO/Eo5sAYtB2/rqaUuOtpInww=
github.com/jarcoal/httpmock v1.3.1/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
Expand All @@ -35,8 +33,6 @@ github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80 h1:6Yzfa6GP0rIo/kUL
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/maxatome/go-testdeep v1.12.0 h1:Ql7Go8Tg0C1D/uMMX59LAoYK7LffeJQ6X2T04nTH68g=
github.com/maxatome/go-testdeep v1.12.0/go.mod h1:lPZc/HAcJMP92l7yI6TRz1aZN5URwUBUAfUNvrclaNM=
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy/FJl/rCYT0+EuS8+Z0z4=
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde h1:x0TT0RDC7UhAVbbWWBzr41ElhJx5tXPWkIHA2HWPRuw=
Expand Down
91 changes: 5 additions & 86 deletions handlers/carbon.go
Original file line number Diff line number Diff line change
@@ -1,94 +1,13 @@
package handlers

import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"strconv"
"time"
)

type CarbonData struct {
Statistics struct {
AdjustedBytes float64 `json:"adjustedBytes"`
Energy float64 `json:"energy"`
Co2 struct {
Grid struct {
Grams float64 `json:"grams"`
Litres float64 `json:"litres"`
} `json:"grid"`
Renewable struct {
Grams float64 `json:"grams"`
Litres float64 `json:"litres"`
} `json:"renewable"`
} `json:"co2"`
} `json:"statistics"`
CleanerThan int `json:"cleanerThan"`
ScanUrl string `json:"scanUrl"`
}

// Function to get the HTML size of the website
func getHtmlSize(ctx context.Context, url string) (int, error) {
client := &http.Client{
Timeout: time.Second * 5,
}
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return 0, err
}
resp, err := client.Do(req)
if err != nil {
return 0, fmt.Errorf("failed to get HTML size: %w", err)
}
defer resp.Body.Close()

body, err := io.ReadAll(resp.Body)
if err != nil {
return 0, fmt.Errorf("failed to read response body: %w", err)
}

return len(body), nil
}

// Function to get the carbon data based on the HTML size
func getCarbonData(ctx context.Context, sizeInBytes int) (*CarbonData, error) {
const carbonDataUrl = "https://api.websitecarbon.com/data"

req, err := http.NewRequestWithContext(ctx, http.MethodGet, carbonDataUrl, nil)
if err != nil {
return nil, err
}
q := req.URL.Query()
q.Add("bytes", strconv.Itoa(sizeInBytes))
q.Add("green", "0")
req.URL.RawQuery = q.Encode()

client := http.Client{
Timeout: time.Second * 5,
}

resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("failed to get carbon data: %w", err)
}
defer resp.Body.Close()

body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read response body: %w", err)
}

var carbonData CarbonData
if err := json.Unmarshal(body, &carbonData); err != nil {
return nil, fmt.Errorf("failed to unmarshal carbon data: %w", err)
}

return &carbonData, nil
}
"github.com/xray-web/web-check-api/checks"
)

func HandleCarbon() http.Handler {
func HandleCarbon(c *checks.Carbon) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
rawURL, err := extractURL(r)
if err != nil {
Expand All @@ -97,13 +16,13 @@ func HandleCarbon() http.Handler {
}

url := rawURL.String()
sizeInBytes, err := getHtmlSize(r.Context(), url)
sizeInBytes, err := c.HtmlSize(r.Context(), url)
if err != nil {
JSONError(w, fmt.Errorf("error getting HTML size: %v", err), http.StatusInternalServerError)
return
}

carbonData, err := getCarbonData(r.Context(), sizeInBytes)
carbonData, err := c.CarbonData(r.Context(), sizeInBytes)
if err != nil {
JSONError(w, fmt.Errorf("error getting carbon data: %v", err), http.StatusInternalServerError)
return
Expand Down
43 changes: 19 additions & 24 deletions handlers/carbon_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,48 +2,43 @@ package handlers

import (
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"testing"

"github.com/jarcoal/httpmock"
"github.com/stretchr/testify/assert"
"github.com/xray-web/web-check-api/checks"
"github.com/xray-web/web-check-api/testutils"
)

func TestHandleCarbon(t *testing.T) {
// t.Parallel()
httpmock.Activate()
defer httpmock.DeactivateAndReset()
t.Parallel()

httpmock.RegisterResponder("GET", "http://test.com",
httpmock.NewStringResponder(200, "<html><body>Test</body></html>"))

htmlSize := len("<html><body>Test</body></html>")

httpmock.RegisterResponder("GET", fmt.Sprintf("https://api.websitecarbon.com/data?bytes=%d&green=0", htmlSize),
httpmock.NewJsonResponderOrPanic(200, map[string]interface{}{
html := `<html><body>Test</body></html>`
client := testutils.MockClient(
testutils.Response(http.StatusOK, []byte(html)),
testutils.ResponseJSON(http.StatusOK, map[string]interface{}{
"statistics": map[string]interface{}{
"adjustedBytes": float64(htmlSize),
"adjustedBytes": float64(len(html)),
"energy": 0.005,
},
}))
}),
)

req := httptest.NewRequest(http.MethodGet, "/carbon?url=http://test.com", nil)
rec := httptest.NewRecorder()
HandleCarbon().ServeHTTP(rec, req)
HandleCarbon(checks.NewCarbon(client)).ServeHTTP(rec, req)

assert.Equal(t, http.StatusOK, rec.Code, "Expected status code 200, but got %d", rec.Code)
assert.Equal(t, http.StatusOK, rec.Code)

var data CarbonData
var data checks.CarbonData
err := json.Unmarshal(rec.Body.Bytes(), &data)
assert.NoError(t, err, "Error unmarshaling response body")
assert.NoError(t, err)

assert.NotEmpty(t, data.ScanUrl, "scanUrl should not be nil")
assert.Equal(t, "http://test.com", data.ScanUrl, "Expected scanUrl to be 'http://test.com', but got %v", data.ScanUrl)
assert.NotEmpty(t, data.ScanUrl)
assert.Equal(t, "http://test.com", data.ScanUrl)

assert.NotEmpty(t, data.Statistics, "statistics should not be nil")
stats := data.Statistics
assert.Equal(t, float64(htmlSize), stats.AdjustedBytes, "Expected adjustedBytes to be %d, but got %v", htmlSize, stats.AdjustedBytes)
assert.Equal(t, 0.005, stats.Energy, "Expected energy to be 0.005, but got %v", stats.Energy)
assert.NotEmpty(t, data.Statistics)
assert.Equal(t, float64(len(html)), data.Statistics.AdjustedBytes)
assert.Equal(t, 0.005, data.Statistics.Energy)
}
2 changes: 1 addition & 1 deletion handlers/cookies_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ func TestHandlerCookies(t *testing.T) {

t.Run("missing URL parameter", func(t *testing.T) {
t.Parallel()
req := httptest.NewRequest(http.MethodGet, "/blocklists", nil)
req := httptest.NewRequest(http.MethodGet, "/cookies", nil)
rec := httptest.NewRecorder()

HandleCookies().ServeHTTP(rec, req)
Expand Down
Loading

0 comments on commit 327e8a9

Please sign in to comment.