Skip to content

Commit

Permalink
Merge pull request #13 from justinfarrelldev/test-coverage
Browse files Browse the repository at this point in the history
Added test coverage checking and brought coverage up to the required amounts.
  • Loading branch information
justinfarrelldev authored Nov 23, 2024
2 parents 1b56206 + 875855c commit a0fa8bc
Show file tree
Hide file tree
Showing 17 changed files with 1,382 additions and 28 deletions.
32 changes: 32 additions & 0 deletions .github/workflows/coverage.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# example 2: on merge to master from pull request (recommended)
name: Check test coverage
on:
pull_request:
branches:
- main

jobs:
coverage:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Go 1.23.x
uses: actions/setup-go@v5
with:
go-version: "1.23.0"
- name: Install dependencies
run: go install
- name: Install bc
run: sudo apt-get install -y bc
- name: Check code coverage
run: |
echo "Checking that code coverage is above 80% within the internal package..."
go test ./internal/... -coverprofile=coverage.out
total_coverage=$(go tool cover -func=coverage.out | grep total | grep -Eo '[0-9]+\.[0-9]+')
echo "Total coverage: $total_coverage"
if (( $(echo "$total_coverage > 80.0" | bc -l) )); then
echo "Code coverage is above 80%!"
else
echo "Code coverage is below 80%!"
exit 1
fi
15 changes: 8 additions & 7 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
{
"recommendations": [
"golang.go",
"streetsidesoftware.code-spell-checker",
"sankethdev.vscode-proto",
"wayou.vscode-todo-highlight"
]
}
"recommendations": [
"golang.go",
"streetsidesoftware.code-spell-checker",
"sankethdev.vscode-proto",
"wayou.vscode-todo-highlight",
"soren.go-coverage-viewer"
]
}
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"Hasher",
"healthcheck",
"healthgrpc",
"jmoiron",
"Ninjaboy",
"proto",
"Protobuf",
Expand Down
5 changes: 5 additions & 0 deletions Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ tasks:
cmds:
- go test ./...

test-coverage:
cmds:
- go test -cover ./internal/...


docs:
cmds:
- swag init # Regenerates docs according to the Swagger specs
138 changes: 138 additions & 0 deletions internal/account/account_handler_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
package account

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

"github.com/DATA-DOG/go-sqlmock"
"github.com/jmoiron/sqlx"
)

// func TestCreateAccountHandler_Success(t *testing.T) {
// db, mock, err := sqlmock.New()
// if err != nil {
// t.Fatalf("an error '%s' was not expected when opening a stub database connection", err)
// }
// defer db.Close()

// account := Account{
// Name: "Test User",
// Info: "Some info",
// Location: "Some location",
// Email: "[email protected]",
// ExperienceLevel: Beginner,
// }

// mock.ExpectQuery("INSERT INTO account \\(name, info, location, email, experience_level\\) VALUES \\(\\$1, \\$2, \\$3, \\$4, \\$5\\)").
// WithArgs(account.Name, account.Info, account.Location, account.Email, account.ExperienceLevel).
// WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(1))

// req, err := http.NewRequest("POST", "/account/create_account", strings.NewReader(`{"name": "Test User", "info": "Some info", "location": "Some location", "email": "[email protected]", "experience_level": 0}`))
// if err != nil {
// t.Fatal(err)
// }

// rr := httptest.NewRecorder()
// sqlxDB := sqlx.NewDb(db, "sqlmock")
// handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// CreateAccountHandler(w, r, sqlxDB)
// })

// handler.ServeHTTP(rr, req)

// if status := rr.Code; status != http.StatusCreated {
// t.Errorf("handler returned wrong status code: got %v want %v", status, http.StatusCreated)
// }
// }

func TestCreateAccountHandler_InvalidMethod(t *testing.T) {
db, _, err := sqlmock.New()
if err != nil {
t.Fatalf("an error '%s' was not expected when opening a stub database connection", err)
}
defer db.Close()

req, err := http.NewRequest("GET", "/account/create_account", strings.NewReader(`{"name": "Test User", "info": "Some info", "location": "Some location", "email": "[email protected]", "experience_level": 0}`))
if err != nil {
t.Fatal(err)
}

rr := httptest.NewRecorder()
sqlxDB := sqlx.NewDb(db, "sqlmock")
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
CreateAccountHandler(w, r, sqlxDB)
})

handler.ServeHTTP(rr, req)

if status := rr.Code; status != http.StatusBadRequest {
t.Errorf("handler returned wrong status code: got %v want %v", status, http.StatusBadRequest)
}

expectedError := "invalid request; request must be a POST request"
if strings.TrimSpace(rr.Body.String()) != expectedError {
t.Errorf("handler returned unexpected body: got %v want %v", strings.TrimSpace(rr.Body.String()), expectedError)
}
}

func TestCreateAccountHandler_DecodeError(t *testing.T) {
db, _, err := sqlmock.New()
if err != nil {
t.Fatalf("an error '%s' was not expected when opening a stub database connection", err)
}
defer db.Close()

req, err := http.NewRequest("POST", "/account/create_account", strings.NewReader(`{ "account": {"name": "Test User", "info": "Some info", "location": "Some location", "email": "[email protected]", "experience_level": "invalid"}, "password": "fake" }`))
if err != nil {
t.Fatal(err)
}

rr := httptest.NewRecorder()
sqlxDB := sqlx.NewDb(db, "sqlmock")
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
CreateAccountHandler(w, r, sqlxDB)
})

handler.ServeHTTP(rr, req)

if status := rr.Code; status != http.StatusInternalServerError {
t.Errorf("handler returned wrong status code: got %v want %v", status, http.StatusInternalServerError)
}

expectedError := "an error occurred while decoding the request body:json: cannot unmarshal string into Go struct field Account.account.experience_level of type account.ExperienceLevel"
if strings.TrimSpace(rr.Body.String()) != expectedError {
t.Errorf("handler returned unexpected body: got %v want %v", strings.TrimSpace(rr.Body.String()), expectedError)
}
}

func TestCreateAccountHandler_EmailRequired(t *testing.T) {
db, _, err := sqlmock.New()
if err != nil {
t.Fatalf("an error '%s' was not expected when opening a stub database connection", err)
}
defer db.Close()

req, err := http.NewRequest("POST", "/account/create_account", strings.NewReader(`{"account": {"name": "Test User", "info": "Some info", "location": "Some location", "experience_level": 0}, "password": "test password" }`))
if err != nil {
t.Fatal(err)
}

rr := httptest.NewRecorder()
sqlxDB := sqlx.NewDb(db, "sqlmock")
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
CreateAccountHandler(w, r, sqlxDB)
})

handler.ServeHTTP(rr, req)

if status := rr.Code; status != http.StatusBadRequest {
t.Errorf("handler returned wrong status code: got %v want %v", status, http.StatusBadRequest)
}

expectedError := "an error occurred while checking whether the email for the account is valid: mail: no address"
if strings.TrimSpace(rr.Body.String()) != expectedError {
t.Errorf("handler returned unexpected body: got %v want %v", strings.TrimSpace(rr.Body.String()), expectedError)
}
}
9 changes: 8 additions & 1 deletion internal/account/create_account.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ func isEmailValid(email string, db *sqlx.DB) (bool, error) {
func CreateAccount(w http.ResponseWriter, r *http.Request, db *sqlx.DB) error {

if r.Method != "POST" {
w.WriteHeader(http.StatusBadRequest)

return errors.New("invalid request; request must be a POST request")
}

Expand Down Expand Up @@ -100,29 +102,34 @@ func CreateAccount(w http.ResponseWriter, r *http.Request, db *sqlx.DB) error {
isValidEmail, err := isEmailValid(account.Account.Email, db)

if err != nil {
w.WriteHeader(http.StatusForbidden)
w.WriteHeader(http.StatusBadRequest)
return err
}

if !isValidEmail {
w.WriteHeader(http.StatusBadRequest)
return errors.New("the provided email is not valid")
}

hashSalt, err := auth.Hasher.GenerateHash([]byte(account.Password), nil)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
log.Println("error saving a password: ", err.Error())
return errors.New("an error occurred while saving the password. Please try again later")
}

err = storeAccount(&account.Account, db)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
log.Println("error saving an account: ", err.Error())
// Different from the one above for debugging purposes
return errors.New("an error occurred while creating the account. Please try again at a later time")
}

err = auth.StoreHashAndSalt(hashSalt, account.Account.Email, db)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)

log.Println("error saving a password: ", err.Error())
// Different from the one above for debugging purposes
return errors.New("an error occurred while saving the password. Please try again at a later time")
Expand Down
Loading

0 comments on commit a0fa8bc

Please sign in to comment.