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) } }) }