Skip to content

Commit

Permalink
Merge pull request #25 from wneessen/fix/24-verify-and-overhaul-the-e…
Browse files Browse the repository at this point in the history
…rror-handling-of-the-different-apis

Fix/24 verify and overhaul the error handling of the different apis
  • Loading branch information
wneessen authored Dec 22, 2022
2 parents ec5038a + f143794 commit 2b0b51a
Show file tree
Hide file tree
Showing 6 changed files with 246 additions and 99 deletions.
70 changes: 35 additions & 35 deletions breach.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,81 +96,81 @@ type APIDate time.Time

// Breaches returns a list of all breaches in the HIBP system
func (b *BreachAPI) Breaches(options ...BreachOption) ([]*Breach, *http.Response, error) {
queryParams := b.setBreachOpts(options...)
apiURL := fmt.Sprintf("%s/breaches", BaseURL)
qp := b.setBreachOpts(options...)
au := fmt.Sprintf("%s/breaches", BaseURL)

hb, hr, err := b.hibp.HTTPResBody(http.MethodGet, apiURL, queryParams)
hb, hr, err := b.hibp.HTTPResBody(http.MethodGet, au, qp)
if err != nil {
return nil, nil, err
return nil, hr, err
}

var breachList []*Breach
if err := json.Unmarshal(hb, &breachList); err != nil {
var bl []*Breach
if err := json.Unmarshal(hb, &bl); err != nil {
return nil, hr, err
}

return breachList, hr, nil
return bl, hr, nil
}

// BreachByName returns a single breached site based on its name
func (b *BreachAPI) BreachByName(n string, options ...BreachOption) (*Breach, *http.Response, error) {
queryParams := b.setBreachOpts(options...)
qp := b.setBreachOpts(options...)

if n == "" {
return nil, nil, fmt.Errorf("no breach name given")
return nil, nil, ErrNoName
}

apiURL := fmt.Sprintf("%s/breach/%s", BaseURL, n)
hb, hr, err := b.hibp.HTTPResBody(http.MethodGet, apiURL, queryParams)
au := fmt.Sprintf("%s/breach/%s", BaseURL, n)
hb, hr, err := b.hibp.HTTPResBody(http.MethodGet, au, qp)
if err != nil {
return nil, nil, err
return nil, hr, err
}

var breachDetails *Breach
if err := json.Unmarshal(hb, &breachDetails); err != nil {
var bd *Breach
if err := json.Unmarshal(hb, &bd); err != nil {
return nil, hr, err
}

return breachDetails, hr, nil
return bd, hr, nil
}

// DataClasses are attribute of a record compromised in a breach. This method returns a list of strings
// with all registered data classes known to HIBP
func (b *BreachAPI) DataClasses() ([]string, *http.Response, error) {
apiURL := fmt.Sprintf("%s/dataclasses", BaseURL)
hb, hr, err := b.hibp.HTTPResBody(http.MethodGet, apiURL, nil)
au := fmt.Sprintf("%s/dataclasses", BaseURL)
hb, hr, err := b.hibp.HTTPResBody(http.MethodGet, au, nil)
if err != nil {
return nil, nil, err
return nil, hr, err
}

var dataClasses []string
if err := json.Unmarshal(hb, &dataClasses); err != nil {
var dc []string
if err := json.Unmarshal(hb, &dc); err != nil {
return nil, hr, err
}

return dataClasses, hr, nil
return dc, hr, nil
}

// BreachedAccount returns a single breached site based on its name
func (b *BreachAPI) BreachedAccount(a string, options ...BreachOption) ([]*Breach, *http.Response, error) {
queryParams := b.setBreachOpts(options...)
qp := b.setBreachOpts(options...)

if a == "" {
return nil, nil, fmt.Errorf("no account id given")
return nil, nil, ErrNoAccountID
}

apiURL := fmt.Sprintf("%s/breachedaccount/%s", BaseURL, a)
hb, hr, err := b.hibp.HTTPResBody(http.MethodGet, apiURL, queryParams)
au := fmt.Sprintf("%s/breachedaccount/%s", BaseURL, a)
hb, hr, err := b.hibp.HTTPResBody(http.MethodGet, au, qp)
if err != nil {
return nil, nil, err
return nil, hr, err
}

var breachDetails []*Breach
if err := json.Unmarshal(hb, &breachDetails); err != nil {
var bd []*Breach
if err := json.Unmarshal(hb, &bd); err != nil {
return nil, hr, err
}

return breachDetails, hr, nil
return bd, hr, nil
}

// WithDomain sets the domain filter for the breaches API
Expand Down Expand Up @@ -205,7 +205,7 @@ func (d *APIDate) UnmarshalJSON(s []byte) error {

pd, err := time.Parse("2006-01-02", ds)
if err != nil {
return fmt.Errorf("failed to convert API date string to time.Time type: %w", err)
return fmt.Errorf("convert API date string to time.Time type: %w", err)
}

*(*time.Time)(d) = pd
Expand All @@ -220,7 +220,7 @@ func (d *APIDate) Time() time.Time {

// setBreachOpts returns a map of default settings and overridden values from different BreachOption
func (b *BreachAPI) setBreachOpts(options ...BreachOption) map[string]string {
queryParams := map[string]string{
qp := map[string]string{
"truncateResponse": "true",
"includeUnverified": "true",
}
Expand All @@ -233,16 +233,16 @@ func (b *BreachAPI) setBreachOpts(options ...BreachOption) map[string]string {
}

if b.domain != "" {
queryParams["domain"] = b.domain
qp["domain"] = b.domain
}

if b.disableTrunc {
queryParams["truncateResponse"] = "false"
qp["truncateResponse"] = "false"
}

if b.noUnverified {
queryParams["includeUnverified"] = "false"
qp["includeUnverified"] = "false"
}

return queryParams
return qp
}
98 changes: 77 additions & 21 deletions breach_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package hibp

import (
"encoding/json"
"errors"
"fmt"
"os"
"testing"
Expand All @@ -14,8 +15,8 @@ const (
invalidDateJSON = `{"date": "202299-10-01"}`
)

// TestBreaches tests the Breaches() method of the breaches API
func TestBreaches(t *testing.T) {
// TestBreachAPI_Breaches tests the Breaches() method of the breaches API
func TestBreachAPI_Breaches(t *testing.T) {
hc := New()
breachList, _, err := hc.BreachAPI.Breaches()
if err != nil {
Expand All @@ -26,20 +27,21 @@ func TestBreaches(t *testing.T) {
}
}

// TestBreachesWithNil tests the Breaches() method of the breaches API with a nil option
func TestBreachesWithNil(t *testing.T) {
// TestBreachAPI_Breaches_WithNIL tests the Breaches() method of the breaches API with a nil option
func TestBreachAPI_Breaches_WithNIL(t *testing.T) {
hc := New()
breachList, _, err := hc.BreachAPI.Breaches(nil)
if err != nil {
t.Error(err)
return
}
if breachList != nil && len(breachList) <= 0 {
t.Error("breaches list returned 0 results")
}
}

// TestBreachesWithDomain tests the Breaches() method of the breaches API for a specific domain
func TestBreachesWithDomain(t *testing.T) {
// TestBreachAPI_Breaches_WithDomain tests the Breaches() method of the breaches API for a specific domain
func TestBreachAPI_Breaches_WithDomain(t *testing.T) {
testTable := []struct {
testName string
domain string
Expand All @@ -55,6 +57,7 @@ func TestBreachesWithDomain(t *testing.T) {
breachList, _, err := hc.BreachAPI.Breaches(WithDomain(tc.domain))
if err != nil {
t.Error(err)
return
}

if breachList == nil && tc.isBreached {
Expand All @@ -75,8 +78,8 @@ func TestBreachesWithDomain(t *testing.T) {
}
}

// TestBreachesWithoutUnverified tests the Breaches() method of the breaches API with the unverified parameter
func TestBreachesWithoutUnverified(t *testing.T) {
// TestBreachAPI_Breaches_WithoutUnverified tests the Breaches() method of the breaches API with the unverified parameter
func TestBreachAPI_Breaches_WithoutUnverified(t *testing.T) {
testTable := []struct {
testName string
domain string
Expand All @@ -94,6 +97,7 @@ func TestBreachesWithoutUnverified(t *testing.T) {
breachList, _, err := hc.BreachAPI.Breaches(WithDomain(tc.domain), WithoutUnverified())
if err != nil {
t.Error(err)
return
}

if breachList == nil && tc.isVerified && tc.isBreached {
Expand All @@ -104,8 +108,8 @@ func TestBreachesWithoutUnverified(t *testing.T) {
}
}

// TestBreachByName tests the BreachByName() method of the breaches API for a specific domain
func TestBreachByName(t *testing.T) {
// TestBreachAPI_BreachByName tests the BreachByName() method of the breaches API for a specific domain
func TestBreachAPI_BreachByName(t *testing.T) {
testTable := []struct {
testName string
breachName string
Expand All @@ -122,6 +126,7 @@ func TestBreachByName(t *testing.T) {
breachDetails, _, err := hc.BreachAPI.BreachByName(tc.breachName)
if err != nil && !tc.shouldFail {
t.Error(err)
return
}

if breachDetails == nil && tc.isBreached {
Expand All @@ -136,20 +141,42 @@ func TestBreachByName(t *testing.T) {
}
}

// TestDataClasses tests the DataClasses() method of the breaches API
func TestDataClasses(t *testing.T) {
// TestBreachAPI_BreachByName_FailedHTTP tests the BreachByName() method with a failing HTTP request
func TestBreachAPI_BreachByName_FailedHTTP(t *testing.T) {
hc := New(WithRateLimitSleep())
_, res, err := hc.BreachAPI.BreachByName("fäiled_invalid")
if err == nil {
t.Errorf("HTTP request was supposed to fail but didn't")
}
if res == nil {
t.Errorf("expected HTTP response but got nil")
}
}

// TestBreachAPI_BreachByName_Errors tests the errors for the BreachByName() method
func TestBreachAPI_BreachByName_Errors(t *testing.T) {
hc := New(WithRateLimitSleep())
_, _, err := hc.BreachAPI.BreachByName("")
if !errors.Is(err, ErrNoName) {
t.Errorf("expected to receive ErrNoName error but didn't")
}
}

// TestBreachAPI_DataClasses tests the DataClasses() method of the breaches API
func TestBreachAPI_DataClasses(t *testing.T) {
hc := New()
classList, _, err := hc.BreachAPI.DataClasses()
if err != nil {
t.Error(err)
return
}
if classList != nil && len(classList) <= 0 {
t.Error("breaches list returned 0 results")
}
}

// TestBreachedAccount tests the BreachedAccount() method of the breaches API
func TestBreachedAccount(t *testing.T) {
// TestBreachAPI_BreachedAccount tests the BreachedAccount() method of the breaches API
func TestBreachAPI_BreachedAccount(t *testing.T) {
testTable := []struct {
testName string
accountName string
Expand Down Expand Up @@ -200,9 +227,35 @@ func TestBreachedAccount(t *testing.T) {
}
}

// TestBreachedAccountWithoutTruncate tests the BreachedAccount() method of the breaches API with the
// TestBreachAPI_BreachedAccount_FailedHTTP tests the BreachedAccount() method of the breaches API with a failing
// HTTP request
func TestBreachAPI_BreachedAccount_FailedHTTP(t *testing.T) {
apiKey := os.Getenv("HIBP_API_KEY")
if apiKey == "" {
t.SkipNow()
}
hc := New(WithAPIKey(apiKey), WithRateLimitSleep())
_, res, err := hc.BreachAPI.BreachedAccount("bröken@invalid_domain.tld")
if err == nil {
t.Error("HTTP request was supposed to fail, but didn't")
}
if res == nil {
t.Errorf("expected HTTP response but got nil")
}
}

// TestBreachAPI_BreachedAccount_Errors tests the errors for the BreachedAccount() method
func TestBreachAPI_BreachedAccount_Errors(t *testing.T) {
hc := New(WithRateLimitSleep())
_, _, err := hc.BreachAPI.BreachedAccount("")
if !errors.Is(err, ErrNoAccountID) {
t.Errorf("expected to receive ErrNoAccountID error but didn't")
}
}

// TestBreachAPI_BreachedAccount_WithoutTruncate tests the BreachedAccount() method of the breaches API with the
// truncateResponse option set to false
func TestBreachedAccountWithoutTruncate(t *testing.T) {
func TestBreachAPI_BreachedAccount_WithoutTruncate(t *testing.T) {
testTable := []struct {
testName string
accountName string
Expand All @@ -211,14 +264,17 @@ func TestBreachedAccountWithoutTruncate(t *testing.T) {
shouldFail bool
}{
{
"account-exists is breached once", "[email protected]", "Adobe",
"adobe.com", false,
"account-exists is breached once", "[email protected]",
"Adobe", "adobe.com", false,
},
{
"multiple-breaches is breached multiple times", "[email protected]",
"Adobe", "adobe.com", false,
},
{
"multiple-breaches is breached multiple times", "multiple-breaches@hibp-integration-tests.com", "Adobe",
"adobe.com", false,
"opt-out is not breached", "opt-out@hibp-integration-tests.com", "",
"", true,
},
{"opt-out is not breached", "[email protected]", "", "", true},
{"empty string should fail", "", "", "", true},
}

Expand Down
Loading

0 comments on commit 2b0b51a

Please sign in to comment.