302 lines
8.8 KiB
Go
302 lines
8.8 KiB
Go
package handlers
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"sync"
|
|
"testing"
|
|
|
|
"github.com/go-chi/chi/v5"
|
|
|
|
"git.threesix.ai/jordan/slack5-1770529463/pkg/app"
|
|
"git.threesix.ai/jordan/slack5-1770529463/pkg/logging"
|
|
"git.threesix.ai/jordan/slack5-1770529463/services/preferences-api/internal/port"
|
|
"git.threesix.ai/jordan/slack5-1770529463/services/preferences-api/internal/service"
|
|
)
|
|
|
|
// mockPreferenceRepository implements port.PreferenceRepository for testing.
|
|
type mockPreferenceRepository struct {
|
|
mu sync.RWMutex
|
|
prefs map[string]map[string]string
|
|
}
|
|
|
|
var _ port.PreferenceRepository = (*mockPreferenceRepository)(nil)
|
|
|
|
func newMockPreferenceRepository() *mockPreferenceRepository {
|
|
return &mockPreferenceRepository{
|
|
prefs: make(map[string]map[string]string),
|
|
}
|
|
}
|
|
|
|
func (m *mockPreferenceRepository) GetByUserID(ctx context.Context, userID string) ([]port.PreferenceRow, error) {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
|
|
userPrefs, ok := m.prefs[userID]
|
|
if !ok {
|
|
return nil, nil
|
|
}
|
|
|
|
rows := make([]port.PreferenceRow, 0, len(userPrefs))
|
|
for k, v := range userPrefs {
|
|
rows = append(rows, port.PreferenceRow{
|
|
UserID: userID,
|
|
Key: k,
|
|
Value: v,
|
|
})
|
|
}
|
|
return rows, nil
|
|
}
|
|
|
|
func (m *mockPreferenceRepository) Upsert(ctx context.Context, userID string, key string, value string) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
|
|
if m.prefs[userID] == nil {
|
|
m.prefs[userID] = make(map[string]string)
|
|
}
|
|
m.prefs[userID][key] = value
|
|
return nil
|
|
}
|
|
|
|
func newTestPreferenceHandler() (*Preference, *mockPreferenceRepository) {
|
|
repo := newMockPreferenceRepository()
|
|
svc := service.NewPreferenceService(repo, logging.Nop())
|
|
handler := NewPreference(svc, logging.Nop())
|
|
return handler, repo
|
|
}
|
|
|
|
func TestPreference_GetPreferences(t *testing.T) {
|
|
handler, repo := newTestPreferenceHandler()
|
|
validUserID := "550e8400-e29b-41d4-a716-446655440000"
|
|
|
|
t.Run("returns defaults for new user", func(t *testing.T) {
|
|
r := chi.NewRouter()
|
|
r.Get("/api/preferences-api/preferences/{user_id}", app.Wrap(handler.GetPreferences))
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/api/preferences-api/preferences/"+validUserID, nil)
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Errorf("expected status 200, got %d", w.Code)
|
|
}
|
|
|
|
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 as object in response")
|
|
}
|
|
|
|
if data["user_id"] != validUserID {
|
|
t.Errorf("expected user_id %s, got %v", validUserID, data["user_id"])
|
|
}
|
|
|
|
prefs, ok := data["preferences"].(map[string]any)
|
|
if !ok {
|
|
t.Fatal("expected 'preferences' field as object in data")
|
|
}
|
|
|
|
if prefs["theme"] != "system" {
|
|
t.Errorf("expected default theme 'system', got %v", prefs["theme"])
|
|
}
|
|
if prefs["language"] != "en" {
|
|
t.Errorf("expected default language 'en', got %v", prefs["language"])
|
|
}
|
|
if prefs["notifications_enabled"] != true {
|
|
t.Errorf("expected default notifications_enabled true, got %v", prefs["notifications_enabled"])
|
|
}
|
|
})
|
|
|
|
t.Run("returns stored preferences merged with defaults", func(t *testing.T) {
|
|
_ = repo.Upsert(context.Background(), validUserID, "theme", "dark")
|
|
|
|
r := chi.NewRouter()
|
|
r.Get("/api/preferences-api/preferences/{user_id}", app.Wrap(handler.GetPreferences))
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/api/preferences-api/preferences/"+validUserID, nil)
|
|
w := httptest.NewRecorder()
|
|
r.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)
|
|
prefs := data["preferences"].(map[string]any)
|
|
|
|
if prefs["theme"] != "dark" {
|
|
t.Errorf("expected theme 'dark', got %v", prefs["theme"])
|
|
}
|
|
})
|
|
|
|
t.Run("returns 400 for invalid user_id", func(t *testing.T) {
|
|
r := chi.NewRouter()
|
|
r.Get("/api/preferences-api/preferences/{user_id}", app.Wrap(handler.GetPreferences))
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/api/preferences-api/preferences/not-a-uuid", nil)
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusBadRequest {
|
|
t.Errorf("expected status 400, got %d", w.Code)
|
|
}
|
|
})
|
|
|
|
t.Run("response has meta field", func(t *testing.T) {
|
|
r := chi.NewRouter()
|
|
r.Get("/api/preferences-api/preferences/{user_id}", app.Wrap(handler.GetPreferences))
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/api/preferences-api/preferences/"+validUserID, nil)
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
|
|
var resp map[string]any
|
|
_ = json.NewDecoder(w.Body).Decode(&resp)
|
|
|
|
if _, ok := resp["meta"]; !ok {
|
|
t.Error("expected 'meta' field in response")
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestPreference_UpdatePreferences(t *testing.T) {
|
|
handler, _ := newTestPreferenceHandler()
|
|
validUserID := "550e8400-e29b-41d4-a716-446655440000"
|
|
|
|
t.Run("updates preferences successfully", func(t *testing.T) {
|
|
r := chi.NewRouter()
|
|
r.Put("/api/preferences-api/preferences/{user_id}", app.Wrap(handler.UpdatePreferences))
|
|
|
|
body, _ := json.Marshal(UpdatePreferencesRequest{
|
|
Preferences: map[string]any{
|
|
"theme": "dark",
|
|
"language": "fr",
|
|
},
|
|
})
|
|
|
|
req := httptest.NewRequest(http.MethodPut, "/api/preferences-api/preferences/"+validUserID, bytes.NewReader(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
w := httptest.NewRecorder()
|
|
r.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)
|
|
prefs := data["preferences"].(map[string]any)
|
|
|
|
if prefs["theme"] != "dark" {
|
|
t.Errorf("expected theme 'dark', got %v", prefs["theme"])
|
|
}
|
|
if prefs["language"] != "fr" {
|
|
t.Errorf("expected language 'fr', got %v", prefs["language"])
|
|
}
|
|
// Default should still be present
|
|
if prefs["notifications_enabled"] != true {
|
|
t.Errorf("expected notifications_enabled true, got %v", prefs["notifications_enabled"])
|
|
}
|
|
})
|
|
|
|
t.Run("returns 400 for unknown key", func(t *testing.T) {
|
|
r := chi.NewRouter()
|
|
r.Put("/api/preferences-api/preferences/{user_id}", app.Wrap(handler.UpdatePreferences))
|
|
|
|
body, _ := json.Marshal(UpdatePreferencesRequest{
|
|
Preferences: map[string]any{
|
|
"font_size": "large",
|
|
},
|
|
})
|
|
|
|
req := httptest.NewRequest(http.MethodPut, "/api/preferences-api/preferences/"+validUserID, bytes.NewReader(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusBadRequest {
|
|
t.Errorf("expected status 400, got %d", w.Code)
|
|
}
|
|
})
|
|
|
|
t.Run("returns 400 for invalid value", func(t *testing.T) {
|
|
r := chi.NewRouter()
|
|
r.Put("/api/preferences-api/preferences/{user_id}", app.Wrap(handler.UpdatePreferences))
|
|
|
|
body, _ := json.Marshal(UpdatePreferencesRequest{
|
|
Preferences: map[string]any{
|
|
"theme": "neon",
|
|
},
|
|
})
|
|
|
|
req := httptest.NewRequest(http.MethodPut, "/api/preferences-api/preferences/"+validUserID, bytes.NewReader(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusBadRequest {
|
|
t.Errorf("expected status 400, got %d", w.Code)
|
|
}
|
|
})
|
|
|
|
t.Run("returns 400 for invalid user_id", func(t *testing.T) {
|
|
r := chi.NewRouter()
|
|
r.Put("/api/preferences-api/preferences/{user_id}", app.Wrap(handler.UpdatePreferences))
|
|
|
|
body, _ := json.Marshal(UpdatePreferencesRequest{
|
|
Preferences: map[string]any{
|
|
"theme": "dark",
|
|
},
|
|
})
|
|
|
|
req := httptest.NewRequest(http.MethodPut, "/api/preferences-api/preferences/bad-id", bytes.NewReader(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusBadRequest {
|
|
t.Errorf("expected status 400, got %d", w.Code)
|
|
}
|
|
})
|
|
|
|
t.Run("handles boolean notifications_enabled", func(t *testing.T) {
|
|
r := chi.NewRouter()
|
|
r.Put("/api/preferences-api/preferences/{user_id}", app.Wrap(handler.UpdatePreferences))
|
|
|
|
body, _ := json.Marshal(UpdatePreferencesRequest{
|
|
Preferences: map[string]any{
|
|
"notifications_enabled": false,
|
|
},
|
|
})
|
|
|
|
req := httptest.NewRequest(http.MethodPut, "/api/preferences-api/preferences/"+validUserID, bytes.NewReader(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
w := httptest.NewRecorder()
|
|
r.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)
|
|
prefs := data["preferences"].(map[string]any)
|
|
|
|
if prefs["notifications_enabled"] != false {
|
|
t.Errorf("expected notifications_enabled false, got %v", prefs["notifications_enabled"])
|
|
}
|
|
})
|
|
}
|