Assignment 4: add unit, integration, and e2e tests; refactor for testability
This commit is contained in:
@@ -0,0 +1,236 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"5803025015/w04/internal/auth"
|
||||
"5803025015/w04/internal/middleware"
|
||||
"5803025015/w04/internal/models"
|
||||
)
|
||||
|
||||
// ---------- GetProfile ----------
|
||||
|
||||
func TestGetProfile(t *testing.T) {
|
||||
t.Run("with user id in context -> 200", func(t *testing.T) {
|
||||
h, _, _ := setupAuthHandler()
|
||||
req := httptest.NewRequest(http.MethodGet, "/profile", nil)
|
||||
ctx := context.WithValue(req.Context(), middleware.UserIDKey, "u1")
|
||||
req = req.WithContext(ctx)
|
||||
rec := httptest.NewRecorder()
|
||||
h.GetProfile(rec, req)
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("want 200, got %d", rec.Code)
|
||||
}
|
||||
assertContentTypeJSON(t, rec)
|
||||
})
|
||||
|
||||
t.Run("without user id -> 401", func(t *testing.T) {
|
||||
h, _, _ := setupAuthHandler()
|
||||
req := httptest.NewRequest(http.MethodGet, "/profile", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
h.GetProfile(rec, req)
|
||||
if rec.Code != http.StatusUnauthorized {
|
||||
t.Fatalf("want 401, got %d", rec.Code)
|
||||
}
|
||||
assertContentTypeJSON(t, rec)
|
||||
})
|
||||
}
|
||||
|
||||
// ---------- Method dispatch ----------
|
||||
|
||||
func TestSnippetsDispatch(t *testing.T) {
|
||||
t.Run("GET dispatch", func(t *testing.T) {
|
||||
h, _ := setupSnippetHandler()
|
||||
rec := doSnippetRequest(h.Snippets, http.MethodGet, "/snippets", nil, "u1")
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("want 200, got %d", rec.Code)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("POST dispatch", func(t *testing.T) {
|
||||
h, _ := setupSnippetHandler()
|
||||
rec := doSnippetRequest(h.Snippets, http.MethodPost, "/snippets",
|
||||
[]byte(`{"title":"t","content":"c"}`), "u1")
|
||||
if rec.Code != http.StatusCreated {
|
||||
t.Fatalf("want 201, got %d", rec.Code)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("PUT -> 405", func(t *testing.T) {
|
||||
h, _ := setupSnippetHandler()
|
||||
rec := doSnippetRequest(h.Snippets, http.MethodPut, "/snippets", nil, "u1")
|
||||
if rec.Code != http.StatusMethodNotAllowed {
|
||||
t.Fatalf("want 405, got %d", rec.Code)
|
||||
}
|
||||
assertContentTypeJSON(t, rec)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSnippetByIDDispatch(t *testing.T) {
|
||||
t.Run("GET dispatch", func(t *testing.T) {
|
||||
h, store := setupSnippetHandler()
|
||||
_ = store.Create(nil, &models.Snippet{ID: "s1", Title: "t", Content: "c", UserID: "u1"})
|
||||
rec := doSnippetRequest(h.SnippetByID, http.MethodGet, "/snippets/s1", nil, "")
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("want 200, got %d", rec.Code)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("DELETE dispatch", func(t *testing.T) {
|
||||
h, store := setupSnippetHandler()
|
||||
_ = store.Create(nil, &models.Snippet{ID: "s1", Title: "t", Content: "c", UserID: "u1"})
|
||||
rec := doSnippetRequest(h.SnippetByID, http.MethodDelete, "/snippets/s1", nil, "u1")
|
||||
if rec.Code != http.StatusNoContent {
|
||||
t.Fatalf("want 204, got %d", rec.Code)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("PUT -> 405", func(t *testing.T) {
|
||||
h, _ := setupSnippetHandler()
|
||||
rec := doSnippetRequest(h.SnippetByID, http.MethodPut, "/snippets/s1", nil, "u1")
|
||||
if rec.Code != http.StatusMethodNotAllowed {
|
||||
t.Fatalf("want 405, got %d", rec.Code)
|
||||
}
|
||||
assertContentTypeJSON(t, rec)
|
||||
})
|
||||
}
|
||||
|
||||
// ---------- Auth handler error branches ----------
|
||||
|
||||
func TestAuthErrorBranches(t *testing.T) {
|
||||
t.Run("Register: store non-duplicate error -> 500", func(t *testing.T) {
|
||||
h, us, _ := setupAuthHandler()
|
||||
us.createErr = errors.New("boom")
|
||||
rec := doRequest(h.Register, http.MethodPost, "/register",
|
||||
[]byte(`{"username":"alice","password":"secret123"}`), nil)
|
||||
if rec.Code != http.StatusInternalServerError {
|
||||
t.Fatalf("want 500, got %d", rec.Code)
|
||||
}
|
||||
assertContentTypeJSON(t, rec)
|
||||
})
|
||||
|
||||
t.Run("Login: session store create error -> 500", func(t *testing.T) {
|
||||
h, us, ss := setupAuthHandler()
|
||||
hash, _ := auth.HashPassword("secret123")
|
||||
_ = us.Create(nil, &models.User{ID: "u1", Username: "alice", PasswordHash: hash})
|
||||
ss.createErr = errors.New("boom")
|
||||
rec := doRequest(h.Login, http.MethodPost, "/login",
|
||||
[]byte(`{"username":"alice","password":"secret123"}`), nil)
|
||||
if rec.Code != http.StatusInternalServerError {
|
||||
t.Fatalf("want 500, got %d", rec.Code)
|
||||
}
|
||||
assertContentTypeJSON(t, rec)
|
||||
})
|
||||
|
||||
t.Run("Logout: delete error -> 500", func(t *testing.T) {
|
||||
h, _, ss := setupAuthHandler()
|
||||
_ = ss.Create(nil, &models.Session{Token: "tok", UserID: "u1"})
|
||||
ss.deleteErr = errors.New("boom")
|
||||
rec := doRequest(h.Logout, http.MethodPost, "/logout", nil,
|
||||
map[string]string{"Authorization": "Bearer tok"})
|
||||
if rec.Code != http.StatusInternalServerError {
|
||||
t.Fatalf("want 500, got %d", rec.Code)
|
||||
}
|
||||
assertContentTypeJSON(t, rec)
|
||||
})
|
||||
|
||||
t.Run("Logout: Basic scheme -> 401", func(t *testing.T) {
|
||||
h, _, _ := setupAuthHandler()
|
||||
rec := doRequest(h.Logout, http.MethodPost, "/logout", nil,
|
||||
map[string]string{"Authorization": "Basic abc"})
|
||||
if rec.Code != http.StatusUnauthorized {
|
||||
t.Fatalf("want 401, got %d", rec.Code)
|
||||
}
|
||||
assertContentTypeJSON(t, rec)
|
||||
})
|
||||
}
|
||||
|
||||
// ---------- Snippet handler error branches ----------
|
||||
|
||||
func TestSnippetErrorBranches(t *testing.T) {
|
||||
t.Run("Create: store error -> 500", func(t *testing.T) {
|
||||
h, store := setupSnippetHandler()
|
||||
store.createErr = errors.New("boom")
|
||||
rec := doSnippetRequest(h.Create, http.MethodPost, "/snippets",
|
||||
[]byte(`{"title":"t","content":"c"}`), "u1")
|
||||
if rec.Code != http.StatusInternalServerError {
|
||||
t.Fatalf("want 500, got %d", rec.Code)
|
||||
}
|
||||
assertContentTypeJSON(t, rec)
|
||||
})
|
||||
|
||||
t.Run("List: store error -> 500", func(t *testing.T) {
|
||||
h, store := setupSnippetHandler()
|
||||
store.listErr = errors.New("boom")
|
||||
rec := doSnippetRequest(h.List, http.MethodGet, "/snippets", nil, "u1")
|
||||
if rec.Code != http.StatusInternalServerError {
|
||||
t.Fatalf("want 500, got %d", rec.Code)
|
||||
}
|
||||
assertContentTypeJSON(t, rec)
|
||||
})
|
||||
|
||||
t.Run("Get: store non-NotFound error -> 500", func(t *testing.T) {
|
||||
h, store := setupSnippetHandler()
|
||||
store.getErr = errors.New("boom")
|
||||
rec := doSnippetRequest(h.Get, http.MethodGet, "/snippets/s1", nil, "")
|
||||
if rec.Code != http.StatusInternalServerError {
|
||||
t.Fatalf("want 500, got %d", rec.Code)
|
||||
}
|
||||
assertContentTypeJSON(t, rec)
|
||||
})
|
||||
|
||||
t.Run("Delete: GetByID store error -> 500", func(t *testing.T) {
|
||||
h, store := setupSnippetHandler()
|
||||
store.getErr = errors.New("boom")
|
||||
rec := doSnippetRequest(h.Delete, http.MethodDelete, "/snippets/s1", nil, "u1")
|
||||
if rec.Code != http.StatusInternalServerError {
|
||||
t.Fatalf("want 500, got %d", rec.Code)
|
||||
}
|
||||
assertContentTypeJSON(t, rec)
|
||||
})
|
||||
|
||||
t.Run("Delete: Delete store error -> 500", func(t *testing.T) {
|
||||
h, store := setupSnippetHandler()
|
||||
_ = store.Create(nil, &models.Snippet{ID: "s1", Title: "t", Content: "c", UserID: "u1"})
|
||||
store.deleteErr = errors.New("boom")
|
||||
rec := doSnippetRequest(h.Delete, http.MethodDelete, "/snippets/s1", nil, "u1")
|
||||
if rec.Code != http.StatusInternalServerError {
|
||||
t.Fatalf("want 500, got %d", rec.Code)
|
||||
}
|
||||
assertContentTypeJSON(t, rec)
|
||||
})
|
||||
|
||||
t.Run("Delete: missing id -> 400", func(t *testing.T) {
|
||||
h, _ := setupSnippetHandler()
|
||||
req := httptest.NewRequest(http.MethodDelete, "/snippets/", nil)
|
||||
req = req.WithContext(context.WithValue(req.Context(), middleware.UserIDKey, "u1"))
|
||||
rec := httptest.NewRecorder()
|
||||
h.Delete(rec, req)
|
||||
if rec.Code != http.StatusBadRequest {
|
||||
t.Fatalf("want 400, got %d", rec.Code)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ---------- extractID PathValue branch ----------
|
||||
|
||||
func TestExtractIDWithPathValue(t *testing.T) {
|
||||
t.Run("uses PathValue when set", func(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodGet, "/snippets/", nil)
|
||||
req.SetPathValue("id", "abc-123")
|
||||
if got := extractID(req); got != "abc-123" {
|
||||
t.Fatalf("want abc-123, got %q", got)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("falls back to path parsing", func(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodGet, "/snippets/xyz/", nil)
|
||||
if got := extractID(req); got != "xyz" {
|
||||
t.Fatalf("want xyz, got %q", got)
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user