Fix test setup and middleware chain - Handler tests now passing
- Fix TestRequireRoleMiddleware by chaining RequireAuth before RequireRole (role context requirement) - Remove unused 'strings' import from testdb.go - Create crussell_test database in Docker setup - Tests now properly initialize authentication context for role-based tests Result: handlers test suite passes (13/13 tests) Remaining failures in admin/auth/bookings/portfolio/scheduling/services/user packages need further investigation (environment setup, database constraints, endpoint initialization)
This commit is contained in:
@@ -0,0 +1,524 @@
|
||||
//go:build test
|
||||
// +build test
|
||||
|
||||
package scheduling
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"crussell/db"
|
||||
"crussell/mw"
|
||||
"crussell/testutils/jwt"
|
||||
"crussell/testutils/testdb"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
)
|
||||
|
||||
func setupTestDB(t *testing.T) func() {
|
||||
t.Helper()
|
||||
|
||||
pool := testdb.Pool(t)
|
||||
testdb.Migrate(t, pool)
|
||||
|
||||
originalDB := db.DB
|
||||
db.DB = pool
|
||||
|
||||
jwt.Init()
|
||||
|
||||
// Seed default working hours
|
||||
seedDefaultWorkingHours(t, pool)
|
||||
|
||||
return func() {
|
||||
db.DB = originalDB
|
||||
pool.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func seedDefaultWorkingHours(t *testing.T, pool *pgxpool.Pool) {
|
||||
t.Helper()
|
||||
|
||||
// Seed 7 days of working hours (Monday=0 to Sunday=6)
|
||||
hours := []struct {
|
||||
weekday int
|
||||
startTime string
|
||||
endTime string
|
||||
isOpen bool
|
||||
}{
|
||||
{0, "09:00", "17:00", true}, // Monday
|
||||
{1, "09:00", "17:00", true}, // Tuesday
|
||||
{2, "09:00", "17:00", true}, // Wednesday
|
||||
{3, "09:00", "17:00", true}, // Thursday
|
||||
{4, "09:00", "17:00", true}, // Friday
|
||||
{5, "10:00", "16:00", true}, // Saturday
|
||||
{6, "00:00", "00:00", false}, // Sunday
|
||||
}
|
||||
|
||||
for _, h := range hours {
|
||||
_, err := pool.Exec(context.Background(), `
|
||||
INSERT INTO working_hours (weekday, start_time, end_time, is_open)
|
||||
VALUES ($1, $2, $3, $4)
|
||||
ON CONFLICT (weekday) DO UPDATE SET start_time = $2, end_time = $3, is_open = $4
|
||||
`, h.weekday, h.startTime, h.endTime, h.isOpen)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to seed working hours: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func makeRequest(handler http.HandlerFunc, method, path string, body interface{}) *httptest.ResponseRecorder {
|
||||
var req *http.Request
|
||||
if body != nil {
|
||||
bodyBytes, _ := json.Marshal(body)
|
||||
req = httptest.NewRequest(method, path, bytes.NewReader(bodyBytes))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
} else {
|
||||
req = httptest.NewRequest(method, path, nil)
|
||||
}
|
||||
w := httptest.NewRecorder()
|
||||
handler.ServeHTTP(w, req)
|
||||
return w
|
||||
}
|
||||
|
||||
func makeAuthRequest(handler http.HandlerFunc, method, path, token string, body interface{}) *httptest.ResponseRecorder {
|
||||
var req *http.Request
|
||||
if body != nil {
|
||||
bodyBytes, _ := json.Marshal(body)
|
||||
req = httptest.NewRequest(method, path, bytes.NewReader(bodyBytes))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
} else {
|
||||
req = httptest.NewRequest(method, path, nil)
|
||||
}
|
||||
if token != "" {
|
||||
req.Header.Set("Authorization", "Bearer "+token)
|
||||
}
|
||||
w := httptest.NewRecorder()
|
||||
handler.ServeHTTP(w, req)
|
||||
return w
|
||||
}
|
||||
|
||||
// --- Tests for GetDefaultHours ---
|
||||
|
||||
func TestScheduling_GetDefaultHours(t *testing.T) {
|
||||
cleanup := setupTestDB(t)
|
||||
defer cleanup()
|
||||
|
||||
handler := http.HandlerFunc(GetDefaultHours)
|
||||
w := makeRequest(handler, "GET", "/api/scheduling/default-hours", nil)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("expected status 200, got %d. body: %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
var response []DefaultHours
|
||||
if err := json.Unmarshal(w.Body.Bytes(), &response); err != nil {
|
||||
t.Fatalf("failed to unmarshal response: %v", err)
|
||||
}
|
||||
|
||||
if len(response) != 7 {
|
||||
t.Errorf("expected 7 days of hours, got %d", len(response))
|
||||
}
|
||||
|
||||
// Verify Monday (weekday 0) has our seeded hours
|
||||
var monday *DefaultHours
|
||||
for i := range response {
|
||||
if response[i].Weekday == 0 {
|
||||
monday = &response[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
if monday == nil {
|
||||
t.Fatal("expected Monday hours in response")
|
||||
}
|
||||
if monday.StartTime != "09:00" {
|
||||
t.Errorf("expected Monday start time 09:00, got %s", monday.StartTime)
|
||||
}
|
||||
if monday.EndTime != "17:00" {
|
||||
t.Errorf("expected Monday end time 17:00, got %s", monday.EndTime)
|
||||
}
|
||||
if !monday.IsOpen {
|
||||
t.Error("expected Monday to be open")
|
||||
}
|
||||
}
|
||||
|
||||
// --- Tests for UpdateDefaultHours ---
|
||||
|
||||
func TestScheduling_UpdateDefaultHours_Admin(t *testing.T) {
|
||||
cleanup := setupTestDB(t)
|
||||
defer cleanup()
|
||||
|
||||
adminToken := jwt.GenerateAdminToken()
|
||||
handler := http.HandlerFunc(UpdateDefaultHours)
|
||||
|
||||
newHours := []DefaultHours{
|
||||
{Weekday: 0, StartTime: "08:00", EndTime: "18:00", IsOpen: true},
|
||||
{Weekday: 1, StartTime: "08:00", EndTime: "18:00", IsOpen: true},
|
||||
{Weekday: 2, StartTime: "08:00", EndTime: "18:00", IsOpen: true},
|
||||
{Weekday: 3, StartTime: "08:00", EndTime: "18:00", IsOpen: true},
|
||||
{Weekday: 4, StartTime: "08:00", EndTime: "18:00", IsOpen: true},
|
||||
{Weekday: 5, StartTime: "09:00", EndTime: "17:00", IsOpen: true},
|
||||
{Weekday: 6, StartTime: "00:00", EndTime: "00:00", IsOpen: false},
|
||||
}
|
||||
|
||||
w := makeAuthRequest(handler, "PUT", "/api/scheduling/default-hours", adminToken, newHours)
|
||||
|
||||
if w.Code != http.StatusNoContent {
|
||||
t.Errorf("expected status 204, got %d. body: %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
// Verify the update persisted
|
||||
var hours []DefaultHours
|
||||
rows, err := db.DB.Query(context.Background(), `SELECT weekday, start_time::text, end_time::text, is_open FROM working_hours ORDER BY weekday`)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to query hours: %v", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
var h DefaultHours
|
||||
if err := rows.Scan(&h.Weekday, &h.StartTime, &h.EndTime, &h.IsOpen); err != nil {
|
||||
t.Fatalf("failed to scan hours: %v", err)
|
||||
}
|
||||
hours = append(hours, h)
|
||||
}
|
||||
|
||||
if hours[0].StartTime != "08:00" {
|
||||
t.Errorf("expected Monday start time 08:00, got %s", hours[0].StartTime)
|
||||
}
|
||||
}
|
||||
|
||||
func TestScheduling_UpdateDefaultHours_NonAdmin(t *testing.T) {
|
||||
cleanup := setupTestDB(t)
|
||||
defer cleanup()
|
||||
|
||||
userToken := jwt.GenerateUserToken("user-123")
|
||||
handler := http.HandlerFunc(UpdateDefaultHours)
|
||||
|
||||
newHours := []DefaultHours{
|
||||
{Weekday: 0, StartTime: "08:00", EndTime: "18:00", IsOpen: true},
|
||||
{Weekday: 1, StartTime: "08:00", EndTime: "18:00", IsOpen: true},
|
||||
{Weekday: 2, StartTime: "08:00", EndTime: "18:00", IsOpen: true},
|
||||
{Weekday: 3, StartTime: "08:00", EndTime: "18:00", IsOpen: true},
|
||||
{Weekday: 4, StartTime: "08:00", EndTime: "18:00", IsOpen: true},
|
||||
{Weekday: 5, StartTime: "09:00", EndTime: "17:00", IsOpen: true},
|
||||
{Weekday: 6, StartTime: "00:00", EndTime: "00:00", IsOpen: false},
|
||||
}
|
||||
|
||||
// Wrap handler with RequireAdmin middleware
|
||||
w := makeAuthRequest(mw.RequireAdmin(UpdateDefaultHours), "PUT", "/api/scheduling/default-hours", userToken, newHours)
|
||||
|
||||
if w.Code != http.StatusForbidden {
|
||||
t.Errorf("expected status 403, got %d. body: %s", w.Code, w.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
// --- Tests for ListExceptionalGroups ---
|
||||
|
||||
func TestScheduling_ListExceptionalGroups(t *testing.T) {
|
||||
cleanup := setupTestDB(t)
|
||||
defer cleanup()
|
||||
|
||||
// Create an exceptional group
|
||||
_, err := db.DB.Exec(context.Background(), `
|
||||
INSERT INTO exceptional_working_hours_groups (name, description)
|
||||
VALUES ('Holiday Hours', 'Christmas holiday schedule')
|
||||
`)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create group: %v", err)
|
||||
}
|
||||
|
||||
handler := http.HandlerFunc(ListExceptionalGroups)
|
||||
w := makeRequest(handler, "GET", "/api/scheduling/exceptional-groups", nil)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("expected status 200, got %d. body: %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
var response []ExceptionalGroup
|
||||
if err := json.Unmarshal(w.Body.Bytes(), &response); err != nil {
|
||||
t.Fatalf("failed to unmarshal response: %v", err)
|
||||
}
|
||||
|
||||
if len(response) == 0 {
|
||||
t.Error("expected at least one group in response")
|
||||
}
|
||||
|
||||
if response[0].Name != "Holiday Hours" {
|
||||
t.Errorf("expected group name 'Holiday Hours', got %s", response[0].Name)
|
||||
}
|
||||
}
|
||||
|
||||
// --- Tests for CreateExceptionalGroup ---
|
||||
|
||||
func TestScheduling_CreateExceptionalGroup_Admin(t *testing.T) {
|
||||
cleanup := setupTestDB(t)
|
||||
defer cleanup()
|
||||
|
||||
adminToken := jwt.GenerateAdminToken()
|
||||
handler := http.HandlerFunc(CreateExceptionalGroup)
|
||||
|
||||
newGroup := ExceptionalGroup{
|
||||
Name: "Summer Hours",
|
||||
Description: "Extended summer schedule",
|
||||
Hours: []ExceptionalHours{
|
||||
{Weekday: 0, StartTime: "08:00", EndTime: "18:00", IsOpen: true},
|
||||
{Weekday: 1, StartTime: "08:00", EndTime: "18:00", IsOpen: true},
|
||||
{Weekday: 2, StartTime: "08:00", EndTime: "18:00", IsOpen: true},
|
||||
{Weekday: 3, StartTime: "08:00", EndTime: "18:00", IsOpen: true},
|
||||
{Weekday: 4, StartTime: "08:00", EndTime: "18:00", IsOpen: true},
|
||||
{Weekday: 5, StartTime: "09:00", EndTime: "17:00", IsOpen: true},
|
||||
{Weekday: 6, StartTime: "00:00", EndTime: "00:00", IsOpen: false},
|
||||
},
|
||||
WeekStarts: []string{"2026-06-01"},
|
||||
}
|
||||
|
||||
w := makeAuthRequest(handler, "POST", "/api/scheduling/exceptional-groups", adminToken, newGroup)
|
||||
|
||||
if w.Code != http.StatusCreated {
|
||||
t.Errorf("expected status 201, got %d. body: %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
var response ExceptionalGroup
|
||||
if err := json.Unmarshal(w.Body.Bytes(), &response); err != nil {
|
||||
t.Fatalf("failed to unmarshal response: %v", err)
|
||||
}
|
||||
|
||||
if response.Name != "Summer Hours" {
|
||||
t.Errorf("expected group name 'Summer Hours', got %s", response.Name)
|
||||
}
|
||||
if len(response.Hours) != 7 {
|
||||
t.Errorf("expected 7 hours, got %d", len(response.Hours))
|
||||
}
|
||||
}
|
||||
|
||||
func TestScheduling_CreateExceptionalGroup_NonAdmin(t *testing.T) {
|
||||
cleanup := setupTestDB(t)
|
||||
defer cleanup()
|
||||
|
||||
userToken := jwt.GenerateUserToken("user-123")
|
||||
handler := http.HandlerFunc(CreateExceptionalGroup)
|
||||
|
||||
newGroup := ExceptionalGroup{
|
||||
Name: "Summer Hours",
|
||||
Description: "Extended summer schedule",
|
||||
Hours: []ExceptionalHours{
|
||||
{Weekday: 0, StartTime: "08:00", EndTime: "18:00", IsOpen: true},
|
||||
{Weekday: 1, StartTime: "08:00", EndTime: "18:00", IsOpen: true},
|
||||
{Weekday: 2, StartTime: "08:00", EndTime: "18:00", IsOpen: true},
|
||||
{Weekday: 3, StartTime: "08:00", EndTime: "18:00", IsOpen: true},
|
||||
{Weekday: 4, StartTime: "08:00", EndTime: "18:00", IsOpen: true},
|
||||
{Weekday: 5, StartTime: "09:00", EndTime: "17:00", IsOpen: true},
|
||||
{Weekday: 6, StartTime: "00:00", EndTime: "00:00", IsOpen: false},
|
||||
},
|
||||
WeekStarts: []string{"2026-06-01"},
|
||||
}
|
||||
|
||||
w := makeAuthRequest(mw.RequireAdmin(CreateExceptionalGroup), "POST", "/api/scheduling/exceptional-groups", userToken, newGroup)
|
||||
|
||||
if w.Code != http.StatusForbidden {
|
||||
t.Errorf("expected status 403, got %d. body: %s", w.Code, w.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
// --- Tests for DeleteExceptionalGroup ---
|
||||
|
||||
func TestScheduling_DeleteExceptionalGroup_Admin(t *testing.T) {
|
||||
cleanup := setupTestDB(t)
|
||||
defer cleanup()
|
||||
|
||||
adminToken := jwt.GenerateAdminToken()
|
||||
|
||||
// Create a group to delete
|
||||
var groupID int
|
||||
err := db.DB.QueryRow(context.Background(), `
|
||||
INSERT INTO exceptional_working_hours_groups (name, description)
|
||||
VALUES ('To Delete', 'Will be deleted')
|
||||
RETURNING id
|
||||
`).Scan(&groupID)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create group: %v", err)
|
||||
}
|
||||
|
||||
handler := http.HandlerFunc(DeleteExceptionalGroup)
|
||||
req := httptest.NewRequest("DELETE", "/api/scheduling/exceptional-groups?id="+string(rune(groupID+'0')), nil)
|
||||
req.Header.Set("Authorization", "Bearer "+adminToken)
|
||||
w := httptest.NewRecorder()
|
||||
handler.ServeHTTP(w, req)
|
||||
|
||||
// The handler expects id as query param but as a proper int
|
||||
// Let's use proper URL query
|
||||
req = httptest.NewRequest("DELETE", "/api/scheduling/exceptional-groups?id="+strconv.Itoa(groupID), nil)
|
||||
req.Header.Set("Authorization", "Bearer "+adminToken)
|
||||
w = httptest.NewRecorder()
|
||||
handler.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusNoContent {
|
||||
t.Errorf("expected status 204, got %d. body: %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
// Verify group was deleted
|
||||
var count int
|
||||
err = db.DB.QueryRow(context.Background(), `SELECT COUNT(*) FROM exceptional_working_hours_groups WHERE id = $1`, groupID).Scan(&count)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to check group: %v", err)
|
||||
}
|
||||
if count != 0 {
|
||||
t.Error("expected group to be deleted")
|
||||
}
|
||||
}
|
||||
|
||||
func TestScheduling_DeleteExceptionalGroup_NonAdmin(t *testing.T) {
|
||||
cleanup := setupTestDB(t)
|
||||
defer cleanup()
|
||||
|
||||
userToken := jwt.GenerateUserToken("user-123")
|
||||
|
||||
handler := mw.RequireAdmin(DeleteExceptionalGroup)
|
||||
req := httptest.NewRequest("DELETE", "/api/scheduling/exceptional-groups?id=1", nil)
|
||||
req.Header.Set("Authorization", "Bearer "+userToken)
|
||||
w := httptest.NewRecorder()
|
||||
handler.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusForbidden {
|
||||
t.Errorf("expected status 403, got %d. body: %s", w.Code, w.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
// --- Tests for GetWorkingHours ---
|
||||
|
||||
func TestScheduling_GetWorkingHours(t *testing.T) {
|
||||
cleanup := setupTestDB(t)
|
||||
defer cleanup()
|
||||
|
||||
handler := http.HandlerFunc(GetWorkingHours)
|
||||
req := httptest.NewRequest("GET", "/api/scheduling/working-hours?start=2026-02-16&end=2026-02-22", nil)
|
||||
w := httptest.NewRecorder()
|
||||
handler.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("expected status 200, got %d. body: %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
var response []DayWorkingHours
|
||||
if err := json.Unmarshal(w.Body.Bytes(), &response); err != nil {
|
||||
t.Fatalf("failed to unmarshal response: %v", err)
|
||||
}
|
||||
|
||||
if len(response) == 0 {
|
||||
t.Error("expected working hours in response")
|
||||
}
|
||||
|
||||
// Verify source is "default" for seeded hours
|
||||
for _, day := range response {
|
||||
if day.Source != "default" {
|
||||
t.Errorf("expected source 'default', got %s", day.Source)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// --- Tests for GetAvailableHours ---
|
||||
|
||||
func TestScheduling_GetAvailableHours(t *testing.T) {
|
||||
cleanup := setupTestDB(t)
|
||||
defer cleanup()
|
||||
|
||||
handler := http.HandlerFunc(GetAvailableHours)
|
||||
req := httptest.NewRequest("GET", "/api/scheduling/available-hours?start=2026-02-16&end=2026-02-22", nil)
|
||||
w := httptest.NewRecorder()
|
||||
handler.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("expected status 200, got %d. body: %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
var response []DayAvailableHours
|
||||
if err := json.Unmarshal(w.Body.Bytes(), &response); err != nil {
|
||||
t.Fatalf("failed to unmarshal response: %v", err)
|
||||
}
|
||||
|
||||
if len(response) == 0 {
|
||||
t.Error("expected available hours in response")
|
||||
}
|
||||
|
||||
// Verify we have slots for open days
|
||||
for _, day := range response {
|
||||
if day.IsOpen {
|
||||
if len(day.Slots) == 0 {
|
||||
t.Error("expected slots for open days")
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Tests for UpdateExceptionalApplications ---
|
||||
|
||||
func TestScheduling_UpdateExceptionalApplications_Admin(t *testing.T) {
|
||||
cleanup := setupTestDB(t)
|
||||
defer cleanup()
|
||||
|
||||
adminToken := jwt.GenerateAdminToken()
|
||||
|
||||
// Create a group
|
||||
var groupID int
|
||||
err := db.DB.QueryRow(context.Background(), `
|
||||
INSERT INTO exceptional_working_hours_groups (name, description)
|
||||
VALUES ('Test Group', 'Test')
|
||||
RETURNING id
|
||||
`).Scan(&groupID)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create group: %v", err)
|
||||
}
|
||||
|
||||
handler := http.HandlerFunc(UpdateExceptionalApplications)
|
||||
|
||||
reqBody := map[string]interface{}{
|
||||
"groupId": groupID,
|
||||
"weekStarts": []string{"2026-03-02", "2026-03-09"},
|
||||
}
|
||||
|
||||
w := makeAuthRequest(handler, "PUT", "/api/scheduling/exceptional-applications", adminToken, reqBody)
|
||||
|
||||
if w.Code != http.StatusNoContent {
|
||||
t.Errorf("expected status 204, got %d. body: %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
// Verify applications were created
|
||||
var count int
|
||||
err = db.DB.QueryRow(context.Background(), `
|
||||
SELECT COUNT(*) FROM exceptional_group_applications WHERE group_id = $1
|
||||
`, groupID).Scan(&count)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to check applications: %v", err)
|
||||
}
|
||||
if count != 2 {
|
||||
t.Errorf("expected 2 applications, got %d", count)
|
||||
}
|
||||
}
|
||||
|
||||
func TestScheduling_UpdateExceptionalApplications_NonAdmin(t *testing.T) {
|
||||
cleanup := setupTestDB(t)
|
||||
defer cleanup()
|
||||
|
||||
userToken := jwt.GenerateUserToken("user-123")
|
||||
handler := mw.RequireAdmin(UpdateExceptionalApplications)
|
||||
|
||||
reqBody := map[string]interface{}{
|
||||
"groupId": 1,
|
||||
"weekStarts": []string{"2026-03-02"},
|
||||
}
|
||||
|
||||
w := makeAuthRequest(handler, "PUT", "/api/scheduling/exceptional-applications", userToken, reqBody)
|
||||
|
||||
if w.Code != http.StatusForbidden {
|
||||
t.Errorf("expected status 403, got %d. body: %s", w.Code, w.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user