refactor: extract API handlers into internal packages
Move HTTP handler logic out of main into internal api and server packages, slimming main to a thin entrypoint. Bundles bug fixes: login nil-panic, empty-password validation, chirp sort ordering, duplicate-email 409, and restricting the file server to the assets directory.
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestWriteError(t *testing.T) {
|
||||
rec := httptest.NewRecorder()
|
||||
WriteError(rec, http.StatusBadRequest, "nope")
|
||||
|
||||
if rec.Code != http.StatusBadRequest {
|
||||
t.Fatalf("status = %d, want %d", rec.Code, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
contentType := rec.Header().Get("Content-Type")
|
||||
|
||||
if !strings.HasPrefix(contentType, "application/json") {
|
||||
t.Fatalf("Content-Type = %q, want application/json", contentType)
|
||||
}
|
||||
|
||||
var body map[string]string
|
||||
|
||||
if err := json.Unmarshal(rec.Body.Bytes(), &body); err != nil {
|
||||
t.Fatalf("response is not valid JSON: %v", err)
|
||||
}
|
||||
|
||||
if body["error"] != "nope" {
|
||||
t.Fatalf("error message = %q, want %q", body["error"], "nope")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteJSONNilPayload(t *testing.T) {
|
||||
rec := httptest.NewRecorder()
|
||||
WriteJSON(rec, http.StatusNoContent, nil)
|
||||
|
||||
if rec.Code != http.StatusNoContent {
|
||||
t.Fatalf("status = %d, want %d", rec.Code, http.StatusNoContent)
|
||||
}
|
||||
|
||||
if rec.Body.Len() != 0 {
|
||||
t.Fatalf("body = %q, want empty", rec.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodeJSON(t *testing.T) {
|
||||
type userRequest struct {
|
||||
Email string `json:"email"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(`{"email":"alice@example.com","password":"hunter2"}`))
|
||||
|
||||
got, err := DecodeJSON[userRequest](req)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("DecodeJSON returned error: %v", err)
|
||||
}
|
||||
|
||||
if got == nil || got.Email != "alice@example.com" || got.Password != "hunter2" {
|
||||
t.Fatalf("DecodeJSON = %+v, want alice@example.com/hunter2", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodeJSONInvalid(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader("not json"))
|
||||
|
||||
_, err := DecodeJSON[struct{}](req)
|
||||
|
||||
if err == nil {
|
||||
t.Fatal("DecodeJSON returned nil error for invalid body")
|
||||
}
|
||||
|
||||
if err.Error() != "Failed to deserialize request" {
|
||||
t.Fatalf("error = %q, want %q", err.Error(), "Failed to deserialize request")
|
||||
}
|
||||
}
|
||||
|
||||
func TestErrorSerializesToJSON(t *testing.T) {
|
||||
rec := httptest.NewRecorder()
|
||||
WriteJSON(rec, http.StatusConflict, Error{Err: "boom"})
|
||||
|
||||
if !strings.Contains(rec.Body.String(), `"boom"`) {
|
||||
t.Fatalf("body = %q, want it to contain the error message", rec.Body.String())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user