322 lines
9.8 KiB
Go
322 lines
9.8 KiB
Go
package handlers
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"github.com/go-chi/chi/v5"
|
|
|
|
"git.threesix.ai/jordan/slack5-1770574304/pkg/app"
|
|
"git.threesix.ai/jordan/slack5-1770574304/pkg/auth"
|
|
"git.threesix.ai/jordan/slack5-1770574304/pkg/logging"
|
|
"git.threesix.ai/jordan/slack5-1770574304/services/preferences-api/internal/adapter/memory"
|
|
"git.threesix.ai/jordan/slack5-1770574304/services/preferences-api/internal/domain"
|
|
"git.threesix.ai/jordan/slack5-1770574304/services/preferences-api/internal/service"
|
|
)
|
|
|
|
func newTestPreferenceHandler() (*Preference, *memory.PreferenceRepository) {
|
|
repo := memory.NewPreferenceRepository()
|
|
svc := service.NewPreferenceService(repo, logging.Nop())
|
|
handler := NewPreference(svc, logging.Nop())
|
|
return handler, repo
|
|
}
|
|
|
|
// withAuthUser adds an authenticated user to the request context.
|
|
func withAuthUser(r *http.Request, userID string, roles ...string) *http.Request {
|
|
user := &auth.User{
|
|
ID: userID,
|
|
Roles: roles,
|
|
}
|
|
ctx := auth.SetUser(r.Context(), user)
|
|
return r.WithContext(ctx)
|
|
}
|
|
|
|
func TestPreference_Get_SelfAccess(t *testing.T) {
|
|
handler, _ := newTestPreferenceHandler()
|
|
|
|
router := chi.NewRouter()
|
|
router.Get("/api/preferences-api/preferences/{user_id}", app.Wrap(handler.Get))
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/api/preferences-api/preferences/user-1", nil)
|
|
req = withAuthUser(req, "user-1")
|
|
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
|
|
if err := json.NewDecoder(w.Body).Decode(&resp); err != nil {
|
|
t.Fatalf("failed to decode response: %v", err)
|
|
}
|
|
|
|
data, ok := resp["data"].(map[string]any)
|
|
if !ok {
|
|
t.Fatal("expected 'data' field in response")
|
|
}
|
|
|
|
// Should return defaults
|
|
if data["theme"] != "system" {
|
|
t.Errorf("expected default theme system, got %v", data["theme"])
|
|
}
|
|
if data["language"] != "en" {
|
|
t.Errorf("expected default language en, got %v", data["language"])
|
|
}
|
|
if data["user_id"] != "user-1" {
|
|
t.Errorf("expected user_id user-1, got %v", data["user_id"])
|
|
}
|
|
// Defaults should not have updated_at
|
|
if _, exists := data["updated_at"]; exists && data["updated_at"] != "" {
|
|
// updated_at may be present but empty for defaults
|
|
}
|
|
}
|
|
|
|
func TestPreference_Get_AdminAccess(t *testing.T) {
|
|
handler, _ := newTestPreferenceHandler()
|
|
|
|
router := chi.NewRouter()
|
|
router.Get("/api/preferences-api/preferences/{user_id}", app.Wrap(handler.Get))
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/api/preferences-api/preferences/user-1", nil)
|
|
req = withAuthUser(req, "admin-user", "admin")
|
|
w := httptest.NewRecorder()
|
|
router.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Errorf("expected status 200 for admin, got %d; body: %s", w.Code, w.Body.String())
|
|
}
|
|
}
|
|
|
|
func TestPreference_Get_Forbidden(t *testing.T) {
|
|
handler, _ := newTestPreferenceHandler()
|
|
|
|
router := chi.NewRouter()
|
|
router.Get("/api/preferences-api/preferences/{user_id}", app.Wrap(handler.Get))
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/api/preferences-api/preferences/user-1", nil)
|
|
req = withAuthUser(req, "other-user")
|
|
w := httptest.NewRecorder()
|
|
router.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusForbidden {
|
|
t.Errorf("expected status 403, got %d; body: %s", w.Code, w.Body.String())
|
|
}
|
|
}
|
|
|
|
func TestPreference_Update_SelfAccess(t *testing.T) {
|
|
handler, _ := newTestPreferenceHandler()
|
|
|
|
router := chi.NewRouter()
|
|
router.Put("/api/preferences-api/preferences/{user_id}", app.Wrap(handler.Update))
|
|
|
|
body := UpdatePreferencesRequest{
|
|
Theme: "dark",
|
|
Language: "fr",
|
|
Notifications: UpdateNotificationsRequest{
|
|
Email: true,
|
|
Push: false,
|
|
Digest: "daily",
|
|
},
|
|
}
|
|
bodyBytes, _ := json.Marshal(body)
|
|
|
|
req := httptest.NewRequest(http.MethodPut, "/api/preferences-api/preferences/user-1", bytes.NewReader(bodyBytes))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
req = withAuthUser(req, "user-1")
|
|
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
|
|
if err := json.NewDecoder(w.Body).Decode(&resp); err != nil {
|
|
t.Fatalf("failed to decode response: %v", err)
|
|
}
|
|
|
|
data, ok := resp["data"].(map[string]any)
|
|
if !ok {
|
|
t.Fatal("expected 'data' field in response")
|
|
}
|
|
|
|
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"])
|
|
}
|
|
|
|
notifications, ok := data["notifications"].(map[string]any)
|
|
if !ok {
|
|
t.Fatal("expected 'notifications' nested object")
|
|
}
|
|
if notifications["push"] != false {
|
|
t.Errorf("expected notifications.push false, got %v", notifications["push"])
|
|
}
|
|
if notifications["digest"] != "daily" {
|
|
t.Errorf("expected notifications.digest daily, got %v", notifications["digest"])
|
|
}
|
|
}
|
|
|
|
func TestPreference_Update_Forbidden(t *testing.T) {
|
|
handler, _ := newTestPreferenceHandler()
|
|
|
|
router := chi.NewRouter()
|
|
router.Put("/api/preferences-api/preferences/{user_id}", app.Wrap(handler.Update))
|
|
|
|
body := UpdatePreferencesRequest{
|
|
Theme: "dark",
|
|
Language: "en",
|
|
Notifications: UpdateNotificationsRequest{
|
|
Email: true,
|
|
Push: true,
|
|
Digest: "weekly",
|
|
},
|
|
}
|
|
bodyBytes, _ := json.Marshal(body)
|
|
|
|
req := httptest.NewRequest(http.MethodPut, "/api/preferences-api/preferences/user-1", bytes.NewReader(bodyBytes))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
req = withAuthUser(req, "other-user")
|
|
w := httptest.NewRecorder()
|
|
router.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusForbidden {
|
|
t.Errorf("expected status 403, got %d; body: %s", w.Code, w.Body.String())
|
|
}
|
|
}
|
|
|
|
func TestPreference_Update_AdminForbidden(t *testing.T) {
|
|
handler, _ := newTestPreferenceHandler()
|
|
|
|
router := chi.NewRouter()
|
|
router.Put("/api/preferences-api/preferences/{user_id}", app.Wrap(handler.Update))
|
|
|
|
body := UpdatePreferencesRequest{
|
|
Theme: "dark",
|
|
Language: "en",
|
|
Notifications: UpdateNotificationsRequest{
|
|
Email: true,
|
|
Push: true,
|
|
Digest: "weekly",
|
|
},
|
|
}
|
|
bodyBytes, _ := json.Marshal(body)
|
|
|
|
req := httptest.NewRequest(http.MethodPut, "/api/preferences-api/preferences/user-1", bytes.NewReader(bodyBytes))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
req = withAuthUser(req, "admin-user", "admin")
|
|
w := httptest.NewRecorder()
|
|
router.ServeHTTP(w, req)
|
|
|
|
// Even admins cannot modify another user's preferences
|
|
if w.Code != http.StatusForbidden {
|
|
t.Errorf("expected status 403 for admin write, got %d; body: %s", w.Code, w.Body.String())
|
|
}
|
|
}
|
|
|
|
func TestPreference_Update_InvalidBody(t *testing.T) {
|
|
handler, _ := newTestPreferenceHandler()
|
|
|
|
router := chi.NewRouter()
|
|
router.Put("/api/preferences-api/preferences/{user_id}", app.Wrap(handler.Update))
|
|
|
|
// Invalid: missing required fields
|
|
bodyBytes := []byte(`{"theme": "dark"}`)
|
|
|
|
req := httptest.NewRequest(http.MethodPut, "/api/preferences-api/preferences/user-1", bytes.NewReader(bodyBytes))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
req = withAuthUser(req, "user-1")
|
|
w := httptest.NewRecorder()
|
|
router.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusBadRequest {
|
|
t.Errorf("expected status 400 for invalid body, got %d; body: %s", w.Code, w.Body.String())
|
|
}
|
|
}
|
|
|
|
func TestPreference_Update_UnknownFields(t *testing.T) {
|
|
handler, _ := newTestPreferenceHandler()
|
|
|
|
router := chi.NewRouter()
|
|
router.Put("/api/preferences-api/preferences/{user_id}", app.Wrap(handler.Update))
|
|
|
|
bodyBytes := []byte(`{"theme":"dark","language":"en","notifications":{"email":true,"push":true,"digest":"weekly"},"unknown_field":"value"}`)
|
|
|
|
req := httptest.NewRequest(http.MethodPut, "/api/preferences-api/preferences/user-1", bytes.NewReader(bodyBytes))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
req = withAuthUser(req, "user-1")
|
|
w := httptest.NewRecorder()
|
|
router.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusBadRequest {
|
|
t.Errorf("expected status 400 for unknown fields, got %d; body: %s", w.Code, w.Body.String())
|
|
}
|
|
}
|
|
|
|
func TestPreference_Update_InvalidThemeValue(t *testing.T) {
|
|
handler, _ := newTestPreferenceHandler()
|
|
|
|
router := chi.NewRouter()
|
|
router.Put("/api/preferences-api/preferences/{user_id}", app.Wrap(handler.Update))
|
|
|
|
bodyBytes := []byte(`{"theme":"invalid","language":"en","notifications":{"email":true,"push":true,"digest":"weekly"}}`)
|
|
|
|
req := httptest.NewRequest(http.MethodPut, "/api/preferences-api/preferences/user-1", bytes.NewReader(bodyBytes))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
req = withAuthUser(req, "user-1")
|
|
w := httptest.NewRecorder()
|
|
router.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusBadRequest {
|
|
t.Errorf("expected status 400 for invalid theme, got %d; body: %s", w.Code, w.Body.String())
|
|
}
|
|
}
|
|
|
|
func TestPreference_Get_ExistingPreferences(t *testing.T) {
|
|
handler, repo := newTestPreferenceHandler()
|
|
|
|
// Seed existing preferences
|
|
existing := &domain.UserPreferences{
|
|
UserID: "user-1",
|
|
Theme: domain.ThemeDark,
|
|
Language: "ja",
|
|
Notifications: domain.NotificationPreferences{
|
|
Email: false,
|
|
Push: false,
|
|
Digest: domain.DigestNone,
|
|
},
|
|
}
|
|
_ = repo.Upsert(nil, existing)
|
|
|
|
router := chi.NewRouter()
|
|
router.Get("/api/preferences-api/preferences/{user_id}", app.Wrap(handler.Get))
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/api/preferences-api/preferences/user-1", nil)
|
|
req = withAuthUser(req, "user-1")
|
|
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
|
|
if err := json.NewDecoder(w.Body).Decode(&resp); err != nil {
|
|
t.Fatalf("failed to decode response: %v", err)
|
|
}
|
|
|
|
data := resp["data"].(map[string]any)
|
|
if data["theme"] != "dark" {
|
|
t.Errorf("expected theme dark, got %v", data["theme"])
|
|
}
|
|
if data["language"] != "ja" {
|
|
t.Errorf("expected language ja, got %v", data["language"])
|
|
}
|
|
}
|