Assignment 4: tests, interface refactor, e2e suite
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"5803025048/W04/internal/models"
|
||||
)
|
||||
|
||||
// fakeLookup adalah fake SessionLookup buatan tangan.
|
||||
type fakeLookup struct {
|
||||
sessions map[string]string // token -> user id
|
||||
nilToken string // token yang mengembalikan (nil, nil)
|
||||
}
|
||||
|
||||
func (f *fakeLookup) Get(_ context.Context, token string) (*models.Session, error) {
|
||||
if token == f.nilToken && token != "" {
|
||||
return nil, nil
|
||||
}
|
||||
uid, ok := f.sessions[token]
|
||||
if !ok {
|
||||
return nil, errors.New("session not found")
|
||||
}
|
||||
return &models.Session{Token: token, UserID: uid}, nil
|
||||
}
|
||||
|
||||
func TestRequireAuth(t *testing.T) {
|
||||
lookup := &fakeLookup{
|
||||
sessions: map[string]string{"valid-token": "user-42"},
|
||||
nilToken: "nil-session-token",
|
||||
}
|
||||
|
||||
// run menjalankan middleware dan mencatat apakah next dipanggil
|
||||
// (lewat closure, bukan dari status code).
|
||||
run := func(authz string, setHeader bool) (rec *httptest.ResponseRecorder, called bool, gotUserID string) {
|
||||
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
called = true
|
||||
gotUserID = GetUserID(r.Context())
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
h := NewAuthMiddleware(lookup).RequireAuth(next)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/snippets", nil)
|
||||
if setHeader {
|
||||
req.Header.Set("Authorization", authz)
|
||||
}
|
||||
rec = httptest.NewRecorder()
|
||||
h.ServeHTTP(rec, req)
|
||||
return rec, called, gotUserID
|
||||
}
|
||||
|
||||
assert401 := func(t *testing.T, rec *httptest.ResponseRecorder, called bool) {
|
||||
t.Helper()
|
||||
if rec.Code != http.StatusUnauthorized {
|
||||
t.Fatalf("status = %d, want 401", rec.Code)
|
||||
}
|
||||
if called {
|
||||
t.Fatal("next handler must not be called")
|
||||
}
|
||||
}
|
||||
|
||||
t.Run("no Authorization header returns 401", func(t *testing.T) {
|
||||
rec, called, _ := run("", false)
|
||||
assert401(t, rec, called)
|
||||
})
|
||||
|
||||
t.Run("Basic scheme returns 401", func(t *testing.T) {
|
||||
rec, called, _ := run("Basic abc", true)
|
||||
assert401(t, rec, called)
|
||||
})
|
||||
|
||||
t.Run("Bearer with empty token returns 401", func(t *testing.T) {
|
||||
rec, called, _ := run("Bearer ", true)
|
||||
assert401(t, rec, called)
|
||||
})
|
||||
|
||||
t.Run("Bearer with only spaces returns 401", func(t *testing.T) {
|
||||
rec, called, _ := run("Bearer ", true)
|
||||
assert401(t, rec, called)
|
||||
})
|
||||
|
||||
t.Run("bare token without scheme returns 401", func(t *testing.T) {
|
||||
rec, called, _ := run("valid-token", true)
|
||||
assert401(t, rec, called)
|
||||
})
|
||||
|
||||
t.Run("unknown token returns 401", func(t *testing.T) {
|
||||
rec, called, _ := run("Bearer does-not-exist", true)
|
||||
assert401(t, rec, called)
|
||||
})
|
||||
|
||||
t.Run("lookup returning nil session returns 401", func(t *testing.T) {
|
||||
rec, called, _ := run("Bearer nil-session-token", true)
|
||||
assert401(t, rec, called)
|
||||
})
|
||||
|
||||
t.Run("valid token calls next with user id in context", func(t *testing.T) {
|
||||
rec, called, uid := run("Bearer valid-token", true)
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("status = %d, want 200", rec.Code)
|
||||
}
|
||||
if !called {
|
||||
t.Fatal("next handler must be called for a valid token")
|
||||
}
|
||||
if uid != "user-42" {
|
||||
t.Fatalf("user id in context = %q, want %q", uid, "user-42")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("401 response is JSON with parsable body", func(t *testing.T) {
|
||||
rec, _, _ := run("Bearer does-not-exist", true)
|
||||
if ct := rec.Header().Get("Content-Type"); !strings.HasPrefix(ct, "application/json") {
|
||||
t.Fatalf("Content-Type = %q, want application/json", ct)
|
||||
}
|
||||
var body map[string]any
|
||||
if err := json.Unmarshal(rec.Body.Bytes(), &body); err != nil {
|
||||
t.Fatalf("401 body is not valid JSON: %v (body=%q)", err, rec.Body.String())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("GetUserID without value returns empty string", func(t *testing.T) {
|
||||
if got := GetUserID(context.Background()); got != "" {
|
||||
t.Fatalf("GetUserID = %q, want empty string", got)
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user