244 lines
7.7 KiB
Go
244 lines
7.7 KiB
Go
package handlers
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"github.com/go-chi/chi/v5"
|
|
|
|
"git.threesix.ai/jordan/slate-test-1770505673/pkg/app"
|
|
"git.threesix.ai/jordan/slate-test-1770505673/pkg/auth"
|
|
"git.threesix.ai/jordan/slate-test-1770505673/pkg/logging"
|
|
"git.threesix.ai/jordan/slate-test-1770505673/services/preferences-api/internal/port"
|
|
"git.threesix.ai/jordan/slate-test-1770505673/services/preferences-api/internal/service"
|
|
)
|
|
|
|
const testUserID = "550e8400-e29b-41d4-a716-446655440000"
|
|
const otherUserID = "550e8400-e29b-41d4-a716-446655440001"
|
|
|
|
// mockPreferenceRepository implements port.PreferenceRepository for testing.
|
|
type mockPreferenceRepository struct {
|
|
data map[string]map[string]string
|
|
}
|
|
|
|
var _ port.PreferenceRepository = (*mockPreferenceRepository)(nil)
|
|
|
|
func newMockRepo() *mockPreferenceRepository {
|
|
return &mockPreferenceRepository{
|
|
data: make(map[string]map[string]string),
|
|
}
|
|
}
|
|
|
|
func (m *mockPreferenceRepository) GetByUserID(_ context.Context, userID string) (map[string]string, error) {
|
|
prefs, ok := m.data[userID]
|
|
if !ok {
|
|
return make(map[string]string), nil
|
|
}
|
|
result := make(map[string]string, len(prefs))
|
|
for k, v := range prefs {
|
|
result[k] = v
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func (m *mockPreferenceRepository) Upsert(_ context.Context, userID string, prefs map[string]string) error {
|
|
if m.data[userID] == nil {
|
|
m.data[userID] = make(map[string]string)
|
|
}
|
|
for k, v := range prefs {
|
|
m.data[userID][k] = v
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func newTestHandler() (*Preference, *mockPreferenceRepository) {
|
|
repo := newMockRepo()
|
|
svc := service.NewPreferenceService(repo, logging.Nop())
|
|
handler := NewPreference(svc, logging.Nop())
|
|
return handler, repo
|
|
}
|
|
|
|
// setupRouter creates a test router with the handler wrapped in app.Wrap.
|
|
func setupRouter(handler *Preference) *chi.Mux {
|
|
r := chi.NewRouter()
|
|
r.Get("/api/preferences-api/preferences/{user_id}", app.Wrap(handler.Get))
|
|
r.Put("/api/preferences-api/preferences/{user_id}", app.Wrap(handler.Update))
|
|
return r
|
|
}
|
|
|
|
// withAuth adds an authenticated user to the request context.
|
|
func withAuth(r *http.Request, userID string) *http.Request {
|
|
ctx := auth.SetUser(r.Context(), &auth.User{ID: userID})
|
|
return r.WithContext(ctx)
|
|
}
|
|
|
|
func TestPreference_Get(t *testing.T) {
|
|
handler, repo := newTestHandler()
|
|
router := setupRouter(handler)
|
|
|
|
t.Run("returns empty prefs for user with no preferences", func(t *testing.T) {
|
|
req := httptest.NewRequest(http.MethodGet, "/api/preferences-api/preferences/"+testUserID, nil)
|
|
req = withAuth(req, testUserID)
|
|
w := httptest.NewRecorder()
|
|
router.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Errorf("expected status 200, got %d", w.Code)
|
|
}
|
|
|
|
var resp map[string]any
|
|
json.NewDecoder(w.Body).Decode(&resp)
|
|
data, ok := resp["data"].(map[string]any)
|
|
if !ok {
|
|
t.Fatal("expected 'data' to be an object")
|
|
}
|
|
if len(data) != 0 {
|
|
t.Errorf("expected empty data, got %v", data)
|
|
}
|
|
})
|
|
|
|
t.Run("returns preferences for user with data", func(t *testing.T) {
|
|
repo.data[testUserID] = map[string]string{"theme": "dark", "language": "en"}
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/api/preferences-api/preferences/"+testUserID, nil)
|
|
req = withAuth(req, testUserID)
|
|
w := httptest.NewRecorder()
|
|
router.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Errorf("expected status 200, got %d", w.Code)
|
|
}
|
|
|
|
var resp map[string]any
|
|
json.NewDecoder(w.Body).Decode(&resp)
|
|
data := resp["data"].(map[string]any)
|
|
if data["theme"] != "dark" {
|
|
t.Errorf("expected theme 'dark', got '%v'", data["theme"])
|
|
}
|
|
if data["language"] != "en" {
|
|
t.Errorf("expected language 'en', got '%v'", data["language"])
|
|
}
|
|
|
|
// Clean up
|
|
delete(repo.data, testUserID)
|
|
})
|
|
|
|
t.Run("returns 400 for invalid UUID", func(t *testing.T) {
|
|
req := httptest.NewRequest(http.MethodGet, "/api/preferences-api/preferences/not-a-uuid", nil)
|
|
req = withAuth(req, testUserID)
|
|
w := httptest.NewRecorder()
|
|
router.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusBadRequest {
|
|
t.Errorf("expected status 400, got %d", w.Code)
|
|
}
|
|
})
|
|
|
|
t.Run("returns 403 for accessing another user's preferences", func(t *testing.T) {
|
|
req := httptest.NewRequest(http.MethodGet, "/api/preferences-api/preferences/"+otherUserID, nil)
|
|
req = withAuth(req, testUserID)
|
|
w := httptest.NewRecorder()
|
|
router.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusForbidden {
|
|
t.Errorf("expected status 403, got %d", w.Code)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestPreference_Update(t *testing.T) {
|
|
handler, _ := newTestHandler()
|
|
router := setupRouter(handler)
|
|
|
|
t.Run("updates preferences successfully", func(t *testing.T) {
|
|
body, _ := json.Marshal(map[string]string{"theme": "dark", "language": "fr"})
|
|
req := httptest.NewRequest(http.MethodPut, "/api/preferences-api/preferences/"+testUserID, bytes.NewReader(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
req = withAuth(req, testUserID)
|
|
w := httptest.NewRecorder()
|
|
router.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Errorf("expected status 200, got %d; body: %s", w.Code, w.Body.String())
|
|
}
|
|
|
|
var resp map[string]any
|
|
json.NewDecoder(w.Body).Decode(&resp)
|
|
data := resp["data"].(map[string]any)
|
|
if data["theme"] != "dark" {
|
|
t.Errorf("expected theme 'dark', got '%v'", data["theme"])
|
|
}
|
|
if data["language"] != "fr" {
|
|
t.Errorf("expected language 'fr', got '%v'", data["language"])
|
|
}
|
|
})
|
|
|
|
t.Run("returns 400 for unknown key", func(t *testing.T) {
|
|
body, _ := json.Marshal(map[string]string{"unknown_key": "val"})
|
|
req := httptest.NewRequest(http.MethodPut, "/api/preferences-api/preferences/"+testUserID, bytes.NewReader(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
req = withAuth(req, testUserID)
|
|
w := httptest.NewRecorder()
|
|
router.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusBadRequest {
|
|
t.Errorf("expected status 400, got %d; body: %s", w.Code, w.Body.String())
|
|
}
|
|
})
|
|
|
|
t.Run("returns 400 for invalid value", func(t *testing.T) {
|
|
body, _ := json.Marshal(map[string]string{"theme": "blue"})
|
|
req := httptest.NewRequest(http.MethodPut, "/api/preferences-api/preferences/"+testUserID, bytes.NewReader(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
req = withAuth(req, testUserID)
|
|
w := httptest.NewRecorder()
|
|
router.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusBadRequest {
|
|
t.Errorf("expected status 400, got %d; body: %s", w.Code, w.Body.String())
|
|
}
|
|
})
|
|
|
|
t.Run("returns 400 for empty body", func(t *testing.T) {
|
|
req := httptest.NewRequest(http.MethodPut, "/api/preferences-api/preferences/"+testUserID, nil)
|
|
req.Header.Set("Content-Type", "application/json")
|
|
req = withAuth(req, testUserID)
|
|
w := httptest.NewRecorder()
|
|
router.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusBadRequest {
|
|
t.Errorf("expected status 400, got %d; body: %s", w.Code, w.Body.String())
|
|
}
|
|
})
|
|
|
|
t.Run("returns 400 for invalid UUID", func(t *testing.T) {
|
|
body, _ := json.Marshal(map[string]string{"theme": "dark"})
|
|
req := httptest.NewRequest(http.MethodPut, "/api/preferences-api/preferences/not-a-uuid", bytes.NewReader(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
req = withAuth(req, testUserID)
|
|
w := httptest.NewRecorder()
|
|
router.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusBadRequest {
|
|
t.Errorf("expected status 400, got %d", w.Code)
|
|
}
|
|
})
|
|
|
|
t.Run("returns 403 for accessing another user's preferences", func(t *testing.T) {
|
|
body, _ := json.Marshal(map[string]string{"theme": "dark"})
|
|
req := httptest.NewRequest(http.MethodPut, "/api/preferences-api/preferences/"+otherUserID, bytes.NewReader(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
req = withAuth(req, testUserID)
|
|
w := httptest.NewRecorder()
|
|
router.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusForbidden {
|
|
t.Errorf("expected status 403, got %d", w.Code)
|
|
}
|
|
})
|
|
}
|