Skip to content

Commit

Permalink
test: user auth helpers (#20)
Browse files Browse the repository at this point in the history
* test: make common test input global variables

* test: pull out user one creation into seperate helper function

* test: add user one setup function to all test cases

* test: correct compose file to remove version

* test: add helper function to tests for logging in user one

* test: make userOneSetup return a struct representing the user creation response

---------

Signed-off-by: Logan Cox <[email protected]>
  • Loading branch information
logan-bobo authored Aug 8, 2024
1 parent a9b1b00 commit 660d090
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 115 deletions.
2 changes: 0 additions & 2 deletions docker-compose-test.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
version: '3.1'

services:
api-test:
image: url-short:test
Expand Down
2 changes: 0 additions & 2 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
version: '3.1'

services:
api:
image: url-short:latest
Expand Down
208 changes: 97 additions & 111 deletions handlers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,14 @@ import (
"url-short/internal/database"
)

var (
userOne = []byte(`{"email": "[email protected]", "password": "test"}`)
userOneUpdatedPassword = []byte(`{"email": "[email protected]", "password":"new-password"}`)
userOneBadPassword = []byte(`{"email": "[email protected]", "password": "testerrrrr"}`)
userBadInput = []byte(`{"gmail":"[email protected]", "auth": "test", "extra_data": "data"}`)
userBadEmail = []byte(`{"email": "test1mail.com", "password": "test"}`)
)

func resetDB(db *sql.DB) error {
provider, err := goose.NewProvider(
goose.DialectPostgres,
Expand All @@ -44,6 +52,49 @@ func resetDB(db *sql.DB) error {
return nil
}

func setupUserOne(apiCfg *apiConfig) (APIUsersResponse, error) {
request, err := http.NewRequest(http.MethodPost, "/api/v1/users", bytes.NewBuffer(userOne))

if err != nil {
return APIUsersResponse{}, err
}

request.Header.Set("Content-Type", "application/json")

response := httptest.NewRecorder()

apiCfg.postAPIUsers(response, request)

got := APIUsersResponse{}

err = json.NewDecoder(response.Body).Decode(&got)

if err != nil {
return APIUsersResponse{}, err
}

return got, nil
}

func loginUserOne(apiCfg *apiConfig) (APIUsersResponse, error) {
loginRequest, _ := http.NewRequest(http.MethodPost, "/api/v1/login", bytes.NewBuffer(userOne))
loginRequest.Header.Set("Content-Type", "application/json")

loginResponse := httptest.NewRecorder()

apiCfg.postAPILogin(loginResponse, loginRequest)

loginGot := APIUsersResponse{}

err := json.NewDecoder(loginResponse.Body).Decode(&loginGot)

if err != nil {
return APIUsersResponse{}, err
}

return loginGot, nil
}

func TestHealthEndpoint(t *testing.T) {
t.Run("test healthz endpoint", func(t *testing.T) {
request, _ := http.NewRequest(http.MethodGet, "/api/v1/healthz", nil)
Expand All @@ -53,9 +104,6 @@ func TestHealthEndpoint(t *testing.T) {

apiCfg.healthz(response, request)

// its not a good idea to use a stuct we already define in our code
// this could introduce a subtle bug where a test could pass because
// we incorrectly altered this struct
got := HealthResponse{}
err := json.NewDecoder(response.Body).Decode(&got)

Expand Down Expand Up @@ -91,42 +139,28 @@ func TestPostUser(t *testing.T) {

dbQueries := database.New(db)

t.Run("test user creation passes with correct parameters", func(t *testing.T) {
requestJSON := []byte(`{"email": "[email protected]", "password": "test"}`)
request, _ := http.NewRequest(http.MethodPost, "/api/v1/users", bytes.NewBuffer(requestJSON))
request.Header.Set("Content-Type", "application/json")

response := httptest.NewRecorder()

apiCfg := apiConfig{
DB: dbQueries,
}

apiCfg.postAPIUsers(response, request)
apiCfg := apiConfig{
DB: dbQueries,
}

got := APIUsersResponse{}
err := json.NewDecoder(response.Body).Decode(&got)
t.Run("test user creation passes with correct parameters", func(t *testing.T) {
userOne, err := setupUserOne(&apiCfg)

if err != nil {
t.Errorf("unable to parse response %q into %q", response.Body, got)
t.Errorf("unable to setup user one due to err %q", err)
}

if got.Email != "[email protected]" {
t.Errorf("unexpected email in response, got %q, wanted %q", got.Email, "[email protected]")
if userOne.Email != "[email protected]" {
t.Errorf("unexpected email in response, got %q, wanted %q", userOne.Email, "[email protected]")
}
})

t.Run("test user creation with bad parameters", func(t *testing.T) {
requestJSON := []byte(`{"gmail":"[email protected]", "auth": "test", "extra_data": "data"}`)
request, _ := http.NewRequest(http.MethodPost, "/api/v1/users", bytes.NewBuffer(requestJSON))
request, _ := http.NewRequest(http.MethodPost, "/api/v1/users", bytes.NewBuffer(userBadInput))
request.Header.Set("Content-Type", "application/json")

response := httptest.NewRecorder()

apiCfg := apiConfig{
DB: dbQueries,
}

apiCfg.postAPIUsers(response, request)

got := errorResponse{}
Expand All @@ -144,16 +178,11 @@ func TestPostUser(t *testing.T) {
})

t.Run("test user creation with no body", func(t *testing.T) {
requestJSON := []byte(``)
request, _ := http.NewRequest(http.MethodPost, "/api/v1/users", bytes.NewBuffer(requestJSON))
request, _ := http.NewRequest(http.MethodPost, "/api/v1/users", bytes.NewBuffer([]byte(``)))
request.Header.Set("Content-Type", "application/json")

response := httptest.NewRecorder()

apiCfg := apiConfig{
DB: dbQueries,
}

apiCfg.postAPIUsers(response, request)

got := errorResponse{}
Expand All @@ -172,16 +201,11 @@ func TestPostUser(t *testing.T) {
})

t.Run("test user creation with bad email address", func(t *testing.T) {
requestJSON := []byte(`{"email": "test1mail.com", "password": "test"}`)
request, _ := http.NewRequest(http.MethodPost, "/api/v1/users", bytes.NewBuffer(requestJSON))
request, _ := http.NewRequest(http.MethodPost, "/api/v1/users", bytes.NewBuffer(userBadEmail))
request.Header.Set("Content-Type", "application/json")

response := httptest.NewRecorder()

apiCfg := apiConfig{
DB: dbQueries,
}

apiCfg.postAPIUsers(response, request)

got := errorResponse{}
Expand All @@ -199,16 +223,11 @@ func TestPostUser(t *testing.T) {
})

t.Run("test a duplicate user can not be created", func(t *testing.T) {
requestJSON := []byte(`{"email": "[email protected]", "password": "test"}`)
request, _ := http.NewRequest(http.MethodPost, "/api/v1/users", bytes.NewBuffer(requestJSON))
request, _ := http.NewRequest(http.MethodPost, "/api/v1/users", bytes.NewBuffer(userOne))
request.Header.Set("Content-Type", "application/json")

response := httptest.NewRecorder()

apiCfg := apiConfig{
DB: dbQueries,
}

apiCfg.postAPIUsers(response, request)

got := errorResponse{}
Expand All @@ -222,7 +241,6 @@ func TestPostUser(t *testing.T) {
if got.Error != want {
t.Errorf("expected duplicate user to fail got %q wanted %q", got.Error, want)
}

})
}

Expand All @@ -234,30 +252,28 @@ func TestPostLogin(t *testing.T) {
t.Errorf("can not open database connection")
}

defer db.Close()

err = resetDB(db)

if err != nil {
t.Errorf("could not resetDB %q", err)
t.Errorf("could not reset DB %q", err)
}

dbQueries := database.New(db)

// setup a user to user for this test case
requestJSON := []byte(`{"email": "[email protected]", "password": "test"}`)
request, _ := http.NewRequest(http.MethodPost, "/api/v1/users", bytes.NewBuffer(requestJSON))
request.Header.Set("Content-Type", "application/json")

apiCfg := apiConfig{
DB: dbQueries,
}

response := httptest.NewRecorder()
_, err = setupUserOne(&apiCfg)

apiCfg.postAPIUsers(response, request)
if err != nil {
t.Errorf("can not set up user for test case with err %q", err)
}

t.Run("test user login fails with incorrect payload", func(t *testing.T) {
requestJSON := []byte(`{"email": "[email protected]", "invalid": "test"}`)
request, _ := http.NewRequest(http.MethodPost, "/api/v1/login", bytes.NewBuffer(requestJSON))
request, _ := http.NewRequest(http.MethodPost, "/api/v1/login", bytes.NewBuffer(userBadInput))
request.Header.Set("Content-Type", "application/json")

response := httptest.NewRecorder()
Expand All @@ -279,8 +295,7 @@ func TestPostLogin(t *testing.T) {
})

t.Run("test user login fails when user can not be found", func(t *testing.T) {
requestJSON := []byte(`{"email": "[email protected]", "password": "test"}`)
request, _ := http.NewRequest(http.MethodPost, "/api/v1/login", bytes.NewBuffer(requestJSON))
request, _ := http.NewRequest(http.MethodPost, "/api/v1/login", bytes.NewBuffer(userBadEmail))
request.Header.Set("Content-Type", "application/json")

response := httptest.NewRecorder()
Expand All @@ -302,8 +317,7 @@ func TestPostLogin(t *testing.T) {
})

t.Run("test user login fails with invalid password", func(t *testing.T) {
requestJSON := []byte(`{"email": "[email protected]", "password": "testerrrrr"}`)
request, _ := http.NewRequest(http.MethodPost, "/api/v1/login", bytes.NewBuffer(requestJSON))
request, _ := http.NewRequest(http.MethodPost, "/api/v1/login", bytes.NewBuffer(userOneBadPassword))
request.Header.Set("Content-Type", "application/json")

response := httptest.NewRecorder()
Expand All @@ -325,8 +339,7 @@ func TestPostLogin(t *testing.T) {
})

t.Run("test user is returned correct ID, Email, Token and a Refresh Token", func(t *testing.T) {
requestJSON := []byte(`{"email": "[email protected]", "password": "test"}`)
request, _ := http.NewRequest(http.MethodPost, "/api/v1/login", bytes.NewBuffer(requestJSON))
request, _ := http.NewRequest(http.MethodPost, "/api/v1/login", bytes.NewBuffer(userOne))
request.Header.Set("Content-Type", "application/json")

response := httptest.NewRecorder()
Expand Down Expand Up @@ -355,6 +368,8 @@ func TestRefreshEndpoint(t *testing.T) {
t.Errorf("can not open database connection")
}

defer db.Close()

err = resetDB(db)

if err != nil {
Expand All @@ -363,41 +378,26 @@ func TestRefreshEndpoint(t *testing.T) {

dbQueries := database.New(db)

// setup a user
requestJSON := []byte(`{"email": "[email protected]", "password": "test"}`)
request, _ := http.NewRequest(http.MethodPost, "/api/v1/users", bytes.NewBuffer(requestJSON))
request.Header.Set("Content-Type", "application/json")

apiCfg := apiConfig{
DB: dbQueries,
}

response := httptest.NewRecorder()

apiCfg.postAPIUsers(response, request)
_, err = setupUserOne(&apiCfg)

t.Run("test valid user can get a new access token based on a valid refresh token", func(t *testing.T) {
// make a request to the login endpoint to be given our token data, refresh and access
loginRequestJSON := []byte(`{"email": "[email protected]", "password": "test"}`)
loginRequest, _ := http.NewRequest(http.MethodPost, "/api/v1/login", bytes.NewBuffer(loginRequestJSON))
loginRequest.Header.Set("Content-Type", "application/json")

loginResponse := httptest.NewRecorder()

apiCfg.postAPILogin(loginResponse, loginRequest)

loginGot := APIUsersResponse{}
if err != nil {
t.Errorf("can not set up user for test case with err %q", err)
}

err := json.NewDecoder(loginResponse.Body).Decode(&loginGot)
userOne, err := loginUserOne(&apiCfg)

if err != nil {
t.Errorf("could not parse login request")
}
if err != nil {
t.Errorf("can not login user one for test case with err %q", err)
}

// use our refresh token to be given a new access token
refreshRequest, _ := http.NewRequest(http.MethodPost, "/api/v1/login", http.NoBody)
t.Run("test valid user can get a new access token based on a valid refresh token", func(t *testing.T) {
refreshRequest, _ := http.NewRequest(http.MethodPost, "/api/v1/refresh", http.NoBody)

buildHeader := fmt.Sprintf("Bearer %s", loginGot.RefreshToken)
buildHeader := fmt.Sprintf("Bearer %s", userOne.RefreshToken)
refreshRequest.Header.Set("Authorization", buildHeader)

refreshResponse := httptest.NewRecorder()
Expand Down Expand Up @@ -426,6 +426,8 @@ func TestPutUser(t *testing.T) {
t.Errorf("can not open database connection")
}

defer db.Close()

err = resetDB(db)

if err != nil {
Expand All @@ -434,42 +436,26 @@ func TestPutUser(t *testing.T) {

dbQueries := database.New(db)

// setup a user
createUserRequestJSON := []byte(`{"email": "[email protected]", "password": "test"}`)
createUserRequest, _ := http.NewRequest(http.MethodPost, "/api/v1/users", bytes.NewBuffer(createUserRequestJSON))
createUserRequest.Header.Set("Content-Type", "application/json")

apiCfg := apiConfig{
DB: dbQueries,
}

createUserResponse := httptest.NewRecorder()

apiCfg.postAPIUsers(createUserResponse, createUserRequest)

// login user endpoint
loginRequestJSON := []byte(`{"email": "[email protected]", "password": "test"}`)
loginRequest, _ := http.NewRequest(http.MethodPost, "/api/v1/login", bytes.NewBuffer(loginRequestJSON))
loginRequest.Header.Set("Content-Type", "application/json")

loginResponse := httptest.NewRecorder()

apiCfg.postAPILogin(loginResponse, loginRequest)
_, err = setupUserOne(&apiCfg)

loginGot := APIUsersResponse{}
if err != nil {
t.Errorf("can not set up user for test case with err %q", err)
}

err = json.NewDecoder(loginResponse.Body).Decode(&loginGot)
userOne, err := loginUserOne(&apiCfg)

if err != nil {
t.Errorf("could not parse login request")
t.Errorf("can not login user one for test case with err %q", err)
}

t.Run("test user can be updated via the put user endpoint", func(t *testing.T) {
putUserRequestJSON := []byte(`{"email": "[email protected]", "password":"new-password"}`)

putUserRequest, _ := http.NewRequest(http.MethodPut, "/api/v1/users", bytes.NewBuffer(putUserRequestJSON))
putUserRequest, _ := http.NewRequest(http.MethodPut, "/api/v1/users", bytes.NewBuffer(userOneUpdatedPassword))

buildHeader := fmt.Sprintf("Bearer %s", loginGot.RefreshToken)
buildHeader := fmt.Sprintf("Bearer %s", userOne.RefreshToken)
putUserRequest.Header.Set("Authorization", buildHeader)

putUserResponse := httptest.NewRecorder()
Expand Down

0 comments on commit 660d090

Please sign in to comment.