- Add full implementation for booking handlers with complete CRUD operations - Implement GetAvailableSlots endpoint to retrieve available booking time slots - Add CreateBooking handler with robust error handling and validation - Implement GetUserBookings endpoint to fetch user's booking history - Add CancelBooking handler with specific error scenarios and authorization checks - Integrate booking service and middleware for authentication and request processing - Add support for date parsing and slot availability checking - Enhance error responses with detailed error messages and appropriate HTTP status codes - Integrate with existing authentication and middleware components
284 lines
6.9 KiB
Go
284 lines
6.9 KiB
Go
package services
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"attune-heart-therapy/internal/config"
|
|
"attune-heart-therapy/internal/models"
|
|
)
|
|
|
|
// MockBookingRepository for testing
|
|
type MockBookingRepository struct {
|
|
bookings map[uint]*models.Booking
|
|
nextID uint
|
|
}
|
|
|
|
func NewMockBookingRepository() *MockBookingRepository {
|
|
return &MockBookingRepository{
|
|
bookings: make(map[uint]*models.Booking),
|
|
nextID: 1,
|
|
}
|
|
}
|
|
|
|
func (m *MockBookingRepository) Create(booking *models.Booking) error {
|
|
booking.ID = m.nextID
|
|
m.nextID++
|
|
m.bookings[booking.ID] = booking
|
|
return nil
|
|
}
|
|
|
|
func (m *MockBookingRepository) GetByID(id uint) (*models.Booking, error) {
|
|
if booking, exists := m.bookings[id]; exists {
|
|
return booking, nil
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
func (m *MockBookingRepository) GetByUserID(userID uint) ([]models.Booking, error) {
|
|
var result []models.Booking
|
|
for _, booking := range m.bookings {
|
|
if booking.UserID == userID {
|
|
result = append(result, *booking)
|
|
}
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func (m *MockBookingRepository) Update(booking *models.Booking) error {
|
|
m.bookings[booking.ID] = booking
|
|
return nil
|
|
}
|
|
|
|
func (m *MockBookingRepository) Delete(id uint) error {
|
|
delete(m.bookings, id)
|
|
return nil
|
|
}
|
|
|
|
func (m *MockBookingRepository) GetUpcomingBookings() ([]models.Booking, error) {
|
|
var result []models.Booking
|
|
now := time.Now()
|
|
for _, booking := range m.bookings {
|
|
if booking.Status == models.BookingStatusScheduled && booking.ScheduledAt.After(now) {
|
|
result = append(result, *booking)
|
|
}
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
// MockScheduleRepository for testing
|
|
type MockScheduleRepository struct {
|
|
schedules map[uint]*models.Schedule
|
|
}
|
|
|
|
func NewMockScheduleRepository() *MockScheduleRepository {
|
|
return &MockScheduleRepository{
|
|
schedules: make(map[uint]*models.Schedule),
|
|
}
|
|
}
|
|
|
|
func (m *MockScheduleRepository) Create(schedule *models.Schedule) error {
|
|
m.schedules[schedule.ID] = schedule
|
|
return nil
|
|
}
|
|
|
|
func (m *MockScheduleRepository) GetAvailable(date time.Time) ([]models.Schedule, error) {
|
|
var result []models.Schedule
|
|
for _, schedule := range m.schedules {
|
|
if schedule.IsAvailable && schedule.BookedCount < schedule.MaxBookings {
|
|
result = append(result, *schedule)
|
|
}
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func (m *MockScheduleRepository) Update(schedule *models.Schedule) error {
|
|
m.schedules[schedule.ID] = schedule
|
|
return nil
|
|
}
|
|
|
|
func (m *MockScheduleRepository) GetByID(id uint) (*models.Schedule, error) {
|
|
if schedule, exists := m.schedules[id]; exists {
|
|
return schedule, nil
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
func (m *MockScheduleRepository) IncrementBookedCount(scheduleID uint) error {
|
|
if schedule, exists := m.schedules[scheduleID]; exists {
|
|
schedule.BookedCount++
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (m *MockScheduleRepository) DecrementBookedCount(scheduleID uint) error {
|
|
if schedule, exists := m.schedules[scheduleID]; exists {
|
|
schedule.BookedCount--
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (m *MockBookingRepository) GetByPaymentID(paymentID string) (*models.Booking, error) {
|
|
for _, booking := range m.bookings {
|
|
if booking.PaymentID == paymentID {
|
|
return booking, nil
|
|
}
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
// MockUserRepository for testing
|
|
type MockUserRepository struct {
|
|
users map[uint]*models.User
|
|
}
|
|
|
|
func NewMockUserRepository() *MockUserRepository {
|
|
return &MockUserRepository{
|
|
users: make(map[uint]*models.User),
|
|
}
|
|
}
|
|
|
|
func (m *MockUserRepository) Create(user *models.User) error {
|
|
m.users[user.ID] = user
|
|
return nil
|
|
}
|
|
|
|
func (m *MockUserRepository) GetByID(id uint) (*models.User, error) {
|
|
if user, exists := m.users[id]; exists {
|
|
return user, nil
|
|
}
|
|
user := &models.User{
|
|
Email: "test@example.com",
|
|
FirstName: "Test",
|
|
}
|
|
user.ID = id
|
|
return user, nil
|
|
}
|
|
|
|
func (m *MockUserRepository) GetByEmail(email string) (*models.User, error) {
|
|
for _, user := range m.users {
|
|
if user.Email == email {
|
|
return user, nil
|
|
}
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
func (m *MockUserRepository) Update(user *models.User) error {
|
|
m.users[user.ID] = user
|
|
return nil
|
|
}
|
|
|
|
func (m *MockUserRepository) GetActiveUsersCount() (int64, error) {
|
|
return int64(len(m.users)), nil
|
|
}
|
|
|
|
// MockNotificationService for testing
|
|
type MockNotificationService struct{}
|
|
|
|
func (m *MockNotificationService) SendWelcomeEmail(user *models.User) error {
|
|
return nil
|
|
}
|
|
|
|
func (m *MockNotificationService) SendPaymentNotification(user *models.User, booking *models.Booking, success bool) error {
|
|
return nil
|
|
}
|
|
|
|
func (m *MockNotificationService) SendMeetingInfo(user *models.User, booking *models.Booking) error {
|
|
return nil
|
|
}
|
|
|
|
func (m *MockNotificationService) SendReminder(user *models.User, booking *models.Booking) error {
|
|
return nil
|
|
}
|
|
|
|
func (m *MockNotificationService) ScheduleReminder(bookingID uint, reminderTime time.Time) error {
|
|
return nil
|
|
}
|
|
|
|
func (m *MockNotificationService) ProcessPendingNotifications() error {
|
|
return nil
|
|
}
|
|
|
|
func TestBookingService_CreateBookingWithJitsiIntegration(t *testing.T) {
|
|
// Setup mock repositories
|
|
bookingRepo := NewMockBookingRepository()
|
|
scheduleRepo := NewMockScheduleRepository()
|
|
userRepo := NewMockUserRepository()
|
|
|
|
// Setup Jitsi service
|
|
jitsiConfig := &config.JitsiConfig{
|
|
BaseURL: "https://meet.jit.si",
|
|
}
|
|
jitsiService := NewJitsiService(jitsiConfig)
|
|
|
|
// Setup mock services
|
|
var paymentService PaymentService
|
|
notificationService := &MockNotificationService{}
|
|
|
|
// Create booking service
|
|
bookingService := NewBookingService(bookingRepo, scheduleRepo, userRepo, jitsiService, paymentService, notificationService)
|
|
|
|
// Create a test schedule
|
|
schedule := &models.Schedule{
|
|
StartTime: time.Now().Add(24 * time.Hour),
|
|
EndTime: time.Now().Add(25 * time.Hour),
|
|
IsAvailable: true,
|
|
MaxBookings: 1,
|
|
BookedCount: 0,
|
|
}
|
|
schedule.ID = 1
|
|
scheduleRepo.schedules[1] = schedule
|
|
|
|
// Create booking request
|
|
req := BookingRequest{
|
|
ScheduleID: 1,
|
|
Amount: 100.0,
|
|
Notes: "Test booking with Jitsi integration",
|
|
}
|
|
|
|
// Create booking
|
|
booking, err := bookingService.CreateBooking(123, req)
|
|
if err != nil {
|
|
t.Fatalf("Expected no error, got %v", err)
|
|
}
|
|
|
|
if booking == nil {
|
|
t.Fatal("Expected booking to be created, got nil")
|
|
}
|
|
|
|
// Verify booking details
|
|
if booking.UserID != 123 {
|
|
t.Errorf("Expected user ID 123, got %d", booking.UserID)
|
|
}
|
|
|
|
if booking.Amount != 100.0 {
|
|
t.Errorf("Expected amount 100.0, got %f", booking.Amount)
|
|
}
|
|
|
|
// Verify Jitsi integration
|
|
if booking.JitsiRoomID == "" {
|
|
t.Error("Expected Jitsi room ID to be set")
|
|
}
|
|
|
|
if booking.JitsiRoomURL == "" {
|
|
t.Error("Expected Jitsi room URL to be set")
|
|
}
|
|
|
|
// Verify URL format
|
|
expectedPrefix := "https://meet.jit.si/"
|
|
if len(booking.JitsiRoomURL) <= len(expectedPrefix) {
|
|
t.Error("Expected room URL to contain room ID")
|
|
}
|
|
|
|
if booking.JitsiRoomURL[:len(expectedPrefix)] != expectedPrefix {
|
|
t.Errorf("Expected room URL to start with %s, got %s", expectedPrefix, booking.JitsiRoomURL)
|
|
}
|
|
|
|
// Verify schedule booking count was incremented
|
|
updatedSchedule, _ := scheduleRepo.GetByID(1)
|
|
if updatedSchedule.BookedCount != 1 {
|
|
t.Errorf("Expected booked count to be 1, got %d", updatedSchedule.BookedCount)
|
|
}
|
|
}
|