feat(services): Implement comprehensive booking service with Jitsi integration
- Add BookingService with core booking management functionality - Implement mock repositories for testing booking service interactions - Create booking integration test with Jitsi room generation - Add methods for creating, retrieving, and managing bookings - Integrate Jitsi service for generating meeting room URLs - Implement schedule management within booking service - Add support for booking creation with user and schedule context - Enhance database layer to support repository retrieval Closes #TICKET_NUMBER (if applicable)
This commit is contained in:
parent
d0117e6ac7
commit
b8dd31b449
@ -6,6 +6,7 @@ import (
|
||||
|
||||
"attune-heart-therapy/internal/config"
|
||||
"attune-heart-therapy/internal/models"
|
||||
"attune-heart-therapy/internal/repositories"
|
||||
|
||||
"gorm.io/driver/postgres"
|
||||
"gorm.io/gorm"
|
||||
@ -110,3 +111,8 @@ func (db *DB) Health() error {
|
||||
}
|
||||
return sqlDB.Ping()
|
||||
}
|
||||
|
||||
// GetRepositories returns all repository instances
|
||||
func (db *DB) GetRepositories() *repositories.Repositories {
|
||||
return repositories.NewRepositories(db.DB)
|
||||
}
|
||||
|
||||
@ -143,9 +143,26 @@ func (s *Server) setupRoutes() {
|
||||
|
||||
// initializeServices sets up all services and handlers
|
||||
func (s *Server) initializeServices() {
|
||||
// Initialize repositories
|
||||
repos := s.db.GetRepositories()
|
||||
|
||||
// Initialize Jitsi service
|
||||
jitsiService := services.NewJitsiService(&s.config.Jitsi)
|
||||
|
||||
// Initialize payment service
|
||||
paymentService := services.NewPaymentService(s.config)
|
||||
|
||||
// Initialize booking service with Jitsi integration
|
||||
bookingService := services.NewBookingService(
|
||||
repos.Booking,
|
||||
repos.Schedule,
|
||||
jitsiService,
|
||||
paymentService,
|
||||
)
|
||||
|
||||
// Store services for later use (if needed)
|
||||
_ = bookingService // Will be used when booking handlers are implemented
|
||||
|
||||
// Initialize payment handler
|
||||
s.paymentHandler = handlers.NewPaymentHandler(paymentService)
|
||||
}
|
||||
|
||||
199
internal/services/booking_integration_test.go
Normal file
199
internal/services/booking_integration_test.go
Normal file
@ -0,0 +1,199 @@
|
||||
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 TestBookingService_CreateBookingWithJitsiIntegration(t *testing.T) {
|
||||
// Setup mock repositories
|
||||
bookingRepo := NewMockBookingRepository()
|
||||
scheduleRepo := NewMockScheduleRepository()
|
||||
|
||||
// Setup Jitsi service
|
||||
jitsiConfig := &config.JitsiConfig{
|
||||
BaseURL: "https://meet.jit.si",
|
||||
}
|
||||
jitsiService := NewJitsiService(jitsiConfig)
|
||||
|
||||
// Setup mock payment service (nil for this test)
|
||||
var paymentService PaymentService
|
||||
|
||||
// Create booking service
|
||||
bookingService := NewBookingService(bookingRepo, scheduleRepo, jitsiService, paymentService)
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
222
internal/services/booking_service.go
Normal file
222
internal/services/booking_service.go
Normal file
@ -0,0 +1,222 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"attune-heart-therapy/internal/models"
|
||||
"attune-heart-therapy/internal/repositories"
|
||||
)
|
||||
|
||||
// bookingService implements the BookingService interface
|
||||
type bookingService struct {
|
||||
bookingRepo repositories.BookingRepository
|
||||
scheduleRepo repositories.ScheduleRepository
|
||||
jitsiService JitsiService
|
||||
paymentService PaymentService
|
||||
}
|
||||
|
||||
// NewBookingService creates a new instance of BookingService
|
||||
func NewBookingService(
|
||||
bookingRepo repositories.BookingRepository,
|
||||
scheduleRepo repositories.ScheduleRepository,
|
||||
jitsiService JitsiService,
|
||||
paymentService PaymentService,
|
||||
) BookingService {
|
||||
return &bookingService{
|
||||
bookingRepo: bookingRepo,
|
||||
scheduleRepo: scheduleRepo,
|
||||
jitsiService: jitsiService,
|
||||
paymentService: paymentService,
|
||||
}
|
||||
}
|
||||
|
||||
// GetAvailableSlots retrieves available time slots for a given date
|
||||
func (s *bookingService) GetAvailableSlots(date time.Time) ([]models.Schedule, error) {
|
||||
slots, err := s.scheduleRepo.GetAvailable(date)
|
||||
if err != nil {
|
||||
log.Printf("Failed to get available slots for date %v: %v", date, err)
|
||||
return nil, fmt.Errorf("failed to get available slots: %w", err)
|
||||
}
|
||||
|
||||
return slots, nil
|
||||
}
|
||||
|
||||
// CreateBooking creates a new booking with Jitsi meeting integration
|
||||
func (s *bookingService) CreateBooking(userID uint, req BookingRequest) (*models.Booking, error) {
|
||||
// Get the schedule to validate availability
|
||||
schedule, err := s.scheduleRepo.GetByID(req.ScheduleID)
|
||||
if err != nil {
|
||||
log.Printf("Failed to get schedule %d: %v", req.ScheduleID, err)
|
||||
return nil, fmt.Errorf("invalid schedule: %w", err)
|
||||
}
|
||||
|
||||
// Check if the schedule is available
|
||||
if !schedule.IsAvailable || schedule.BookedCount >= schedule.MaxBookings {
|
||||
return nil, fmt.Errorf("schedule slot is not available")
|
||||
}
|
||||
|
||||
// Create the booking record first (without Jitsi details)
|
||||
booking := &models.Booking{
|
||||
UserID: userID,
|
||||
ScheduledAt: schedule.StartTime,
|
||||
Duration: 60, // Default duration, can be made configurable
|
||||
Status: models.BookingStatusScheduled,
|
||||
Amount: req.Amount,
|
||||
Notes: req.Notes,
|
||||
PaymentStatus: models.PaymentStatusPending,
|
||||
}
|
||||
|
||||
// Save the booking to get an ID
|
||||
if err := s.bookingRepo.Create(booking); err != nil {
|
||||
log.Printf("Failed to create booking for user %d: %v", userID, err)
|
||||
return nil, fmt.Errorf("failed to create booking: %w", err)
|
||||
}
|
||||
|
||||
// Create Jitsi meeting after successful booking creation
|
||||
jitsiMeeting, err := s.jitsiService.CreateMeeting(booking.ID, booking.ScheduledAt)
|
||||
if err != nil {
|
||||
log.Printf("Failed to create Jitsi meeting for booking %d: %v", booking.ID, err)
|
||||
// Don't fail the booking creation if Jitsi fails, but log the error
|
||||
// The meeting can be created later or manually
|
||||
log.Printf("Booking %d created without Jitsi meeting due to error: %v", booking.ID, err)
|
||||
} else {
|
||||
// Update booking with Jitsi meeting details
|
||||
booking.JitsiRoomID = jitsiMeeting.RoomID
|
||||
booking.JitsiRoomURL = jitsiMeeting.RoomURL
|
||||
|
||||
if err := s.bookingRepo.Update(booking); err != nil {
|
||||
log.Printf("Failed to update booking %d with Jitsi details: %v", booking.ID, err)
|
||||
// Log error but don't fail the booking creation
|
||||
} else {
|
||||
log.Printf("Successfully created Jitsi meeting for booking %d: Room ID %s", booking.ID, jitsiMeeting.RoomID)
|
||||
}
|
||||
}
|
||||
|
||||
// Increment the booked count for the schedule
|
||||
if err := s.scheduleRepo.IncrementBookedCount(req.ScheduleID); err != nil {
|
||||
log.Printf("Failed to increment booked count for schedule %d: %v", req.ScheduleID, err)
|
||||
// This is not critical, continue with the booking
|
||||
}
|
||||
|
||||
return booking, nil
|
||||
}
|
||||
|
||||
// GetUserBookings retrieves all bookings for a specific user
|
||||
func (s *bookingService) GetUserBookings(userID uint) ([]models.Booking, error) {
|
||||
bookings, err := s.bookingRepo.GetByUserID(userID)
|
||||
if err != nil {
|
||||
log.Printf("Failed to get bookings for user %d: %v", userID, err)
|
||||
return nil, fmt.Errorf("failed to get user bookings: %w", err)
|
||||
}
|
||||
|
||||
return bookings, nil
|
||||
}
|
||||
|
||||
// CancelBooking cancels a booking and cleans up associated resources
|
||||
func (s *bookingService) CancelBooking(userID, bookingID uint) error {
|
||||
// Get the booking to validate ownership and status
|
||||
booking, err := s.bookingRepo.GetByID(bookingID)
|
||||
if err != nil {
|
||||
log.Printf("Failed to get booking %d: %v", bookingID, err)
|
||||
return fmt.Errorf("booking not found: %w", err)
|
||||
}
|
||||
|
||||
// Validate ownership
|
||||
if booking.UserID != userID {
|
||||
return fmt.Errorf("unauthorized: booking does not belong to user")
|
||||
}
|
||||
|
||||
// Check if booking can be cancelled
|
||||
if !booking.CanBeCancelled() {
|
||||
return fmt.Errorf("booking cannot be cancelled")
|
||||
}
|
||||
|
||||
// Update booking status
|
||||
booking.Status = models.BookingStatusCancelled
|
||||
|
||||
if err := s.bookingRepo.Update(booking); err != nil {
|
||||
log.Printf("Failed to update booking %d status to cancelled: %v", bookingID, err)
|
||||
return fmt.Errorf("failed to cancel booking: %w", err)
|
||||
}
|
||||
|
||||
// Clean up Jitsi meeting if it exists
|
||||
if booking.JitsiRoomID != "" {
|
||||
if err := s.jitsiService.DeleteMeeting(booking.JitsiRoomID); err != nil {
|
||||
log.Printf("Failed to delete Jitsi meeting %s for cancelled booking %d: %v",
|
||||
booking.JitsiRoomID, bookingID, err)
|
||||
// Don't fail the cancellation if Jitsi cleanup fails
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("Successfully cancelled booking %d for user %d", bookingID, userID)
|
||||
return nil
|
||||
}
|
||||
|
||||
// RescheduleBooking reschedules a booking to a new time slot
|
||||
func (s *bookingService) RescheduleBooking(userID, bookingID uint, newScheduleID uint) error {
|
||||
// Get the existing booking
|
||||
booking, err := s.bookingRepo.GetByID(bookingID)
|
||||
if err != nil {
|
||||
log.Printf("Failed to get booking %d: %v", bookingID, err)
|
||||
return fmt.Errorf("booking not found: %w", err)
|
||||
}
|
||||
|
||||
// Validate ownership
|
||||
if booking.UserID != userID {
|
||||
return fmt.Errorf("unauthorized: booking does not belong to user")
|
||||
}
|
||||
|
||||
// Check if booking can be rescheduled
|
||||
if !booking.CanBeRescheduled() {
|
||||
return fmt.Errorf("booking cannot be rescheduled")
|
||||
}
|
||||
|
||||
// Get the new schedule
|
||||
newSchedule, err := s.scheduleRepo.GetByID(newScheduleID)
|
||||
if err != nil {
|
||||
log.Printf("Failed to get new schedule %d: %v", newScheduleID, err)
|
||||
return fmt.Errorf("invalid new schedule: %w", err)
|
||||
}
|
||||
|
||||
// Check if the new schedule is available
|
||||
if !newSchedule.IsAvailable || newSchedule.BookedCount >= newSchedule.MaxBookings {
|
||||
return fmt.Errorf("new schedule slot is not available")
|
||||
}
|
||||
|
||||
// Clean up old Jitsi meeting
|
||||
if booking.JitsiRoomID != "" {
|
||||
if err := s.jitsiService.DeleteMeeting(booking.JitsiRoomID); err != nil {
|
||||
log.Printf("Failed to delete old Jitsi meeting %s: %v", booking.JitsiRoomID, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Create new Jitsi meeting
|
||||
jitsiMeeting, err := s.jitsiService.CreateMeeting(booking.ID, newSchedule.StartTime)
|
||||
if err != nil {
|
||||
log.Printf("Failed to create new Jitsi meeting for rescheduled booking %d: %v", bookingID, err)
|
||||
// Continue with rescheduling even if Jitsi fails
|
||||
booking.JitsiRoomID = ""
|
||||
booking.JitsiRoomURL = ""
|
||||
} else {
|
||||
booking.JitsiRoomID = jitsiMeeting.RoomID
|
||||
booking.JitsiRoomURL = jitsiMeeting.RoomURL
|
||||
}
|
||||
|
||||
// Update booking with new schedule details
|
||||
booking.ScheduledAt = newSchedule.StartTime
|
||||
|
||||
if err := s.bookingRepo.Update(booking); err != nil {
|
||||
log.Printf("Failed to update rescheduled booking %d: %v", bookingID, err)
|
||||
return fmt.Errorf("failed to reschedule booking: %w", err)
|
||||
}
|
||||
|
||||
// Update schedule counts
|
||||
if err := s.scheduleRepo.IncrementBookedCount(newScheduleID); err != nil {
|
||||
log.Printf("Failed to increment booked count for new schedule %d: %v", newScheduleID, err)
|
||||
}
|
||||
|
||||
log.Printf("Successfully rescheduled booking %d for user %d to schedule %d", bookingID, userID, newScheduleID)
|
||||
return nil
|
||||
}
|
||||
92
internal/services/jitsi_service.go
Normal file
92
internal/services/jitsi_service.go
Normal file
@ -0,0 +1,92 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"attune-heart-therapy/internal/config"
|
||||
)
|
||||
|
||||
// jitsiService implements the JitsiService interface
|
||||
type jitsiService struct {
|
||||
config *config.JitsiConfig
|
||||
}
|
||||
|
||||
// NewJitsiService creates a new instance of JitsiService
|
||||
func NewJitsiService(cfg *config.JitsiConfig) JitsiService {
|
||||
return &jitsiService{
|
||||
config: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateMeeting creates a new Jitsi meeting room for a booking
|
||||
func (j *jitsiService) CreateMeeting(bookingID uint, scheduledAt time.Time) (*JitsiMeeting, error) {
|
||||
// Generate a unique room ID
|
||||
roomID, err := j.generateRoomID(bookingID)
|
||||
if err != nil {
|
||||
log.Printf("Failed to generate room ID for booking %d: %v", bookingID, err)
|
||||
return nil, fmt.Errorf("failed to generate room ID: %w", err)
|
||||
}
|
||||
|
||||
// Generate the meeting URL
|
||||
roomURL := j.GetMeetingURL(roomID)
|
||||
|
||||
meeting := &JitsiMeeting{
|
||||
RoomID: roomID,
|
||||
RoomURL: roomURL,
|
||||
}
|
||||
|
||||
log.Printf("Created Jitsi meeting for booking %d: Room ID %s", bookingID, roomID)
|
||||
return meeting, nil
|
||||
}
|
||||
|
||||
// GetMeetingURL generates the full Jitsi meeting URL for a given room ID
|
||||
func (j *jitsiService) GetMeetingURL(roomID string) string {
|
||||
baseURL := j.config.BaseURL
|
||||
if baseURL == "" {
|
||||
// Default to meet.jit.si if no base URL is configured
|
||||
baseURL = "https://meet.jit.si"
|
||||
}
|
||||
|
||||
// Ensure the base URL doesn't end with a slash
|
||||
if baseURL[len(baseURL)-1] == '/' {
|
||||
baseURL = baseURL[:len(baseURL)-1]
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s/%s", baseURL, roomID)
|
||||
}
|
||||
|
||||
// DeleteMeeting handles cleanup of a Jitsi meeting room
|
||||
// Note: Jitsi Meet doesn't require explicit room deletion as rooms are ephemeral
|
||||
// This method is implemented for interface compliance and future extensibility
|
||||
func (j *jitsiService) DeleteMeeting(roomID string) error {
|
||||
// Jitsi Meet rooms are ephemeral and don't require explicit deletion
|
||||
// However, we can log the deletion for audit purposes
|
||||
log.Printf("Meeting room %s marked for cleanup", roomID)
|
||||
|
||||
// In the future, if using Jitsi as a Service (JaaS) or custom deployment,
|
||||
// this method could make API calls to clean up resources
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// generateRoomID creates a unique room identifier for the meeting
|
||||
func (j *jitsiService) generateRoomID(bookingID uint) (string, error) {
|
||||
// Generate a random component for uniqueness
|
||||
randomBytes := make([]byte, 8)
|
||||
_, err := rand.Read(randomBytes)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to generate random bytes: %w", err)
|
||||
}
|
||||
|
||||
randomHex := hex.EncodeToString(randomBytes)
|
||||
|
||||
// Create a room ID that includes the booking ID and timestamp for uniqueness
|
||||
timestamp := time.Now().Unix()
|
||||
roomID := fmt.Sprintf("booking-%d-%d-%s", bookingID, timestamp, randomHex)
|
||||
|
||||
return roomID, nil
|
||||
}
|
||||
95
internal/services/jitsi_service_test.go
Normal file
95
internal/services/jitsi_service_test.go
Normal file
@ -0,0 +1,95 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"attune-heart-therapy/internal/config"
|
||||
)
|
||||
|
||||
func TestJitsiService_CreateMeeting(t *testing.T) {
|
||||
// Setup test config
|
||||
cfg := &config.JitsiConfig{
|
||||
BaseURL: "https://meet.jit.si",
|
||||
}
|
||||
|
||||
service := NewJitsiService(cfg)
|
||||
|
||||
// Test creating a meeting
|
||||
bookingID := uint(123)
|
||||
scheduledAt := time.Now().Add(24 * time.Hour)
|
||||
|
||||
meeting, err := service.CreateMeeting(bookingID, scheduledAt)
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got %v", err)
|
||||
}
|
||||
|
||||
if meeting == nil {
|
||||
t.Fatal("Expected meeting to be created, got nil")
|
||||
}
|
||||
|
||||
if meeting.RoomID == "" {
|
||||
t.Error("Expected room ID to be generated")
|
||||
}
|
||||
|
||||
if meeting.RoomURL == "" {
|
||||
t.Error("Expected room URL to be generated")
|
||||
}
|
||||
|
||||
// Verify URL format
|
||||
expectedPrefix := "https://meet.jit.si/"
|
||||
if len(meeting.RoomURL) <= len(expectedPrefix) {
|
||||
t.Error("Expected room URL to contain room ID")
|
||||
}
|
||||
|
||||
if meeting.RoomURL[:len(expectedPrefix)] != expectedPrefix {
|
||||
t.Errorf("Expected room URL to start with %s, got %s", expectedPrefix, meeting.RoomURL)
|
||||
}
|
||||
}
|
||||
|
||||
func TestJitsiService_GetMeetingURL(t *testing.T) {
|
||||
cfg := &config.JitsiConfig{
|
||||
BaseURL: "https://meet.jit.si",
|
||||
}
|
||||
|
||||
service := NewJitsiService(cfg)
|
||||
|
||||
roomID := "test-room-123"
|
||||
url := service.GetMeetingURL(roomID)
|
||||
|
||||
expected := "https://meet.jit.si/test-room-123"
|
||||
if url != expected {
|
||||
t.Errorf("Expected URL %s, got %s", expected, url)
|
||||
}
|
||||
}
|
||||
|
||||
func TestJitsiService_GetMeetingURL_DefaultBaseURL(t *testing.T) {
|
||||
// Test with empty base URL (should use default)
|
||||
cfg := &config.JitsiConfig{
|
||||
BaseURL: "",
|
||||
}
|
||||
|
||||
service := NewJitsiService(cfg)
|
||||
|
||||
roomID := "test-room-456"
|
||||
url := service.GetMeetingURL(roomID)
|
||||
|
||||
expected := "https://meet.jit.si/test-room-456"
|
||||
if url != expected {
|
||||
t.Errorf("Expected URL %s, got %s", expected, url)
|
||||
}
|
||||
}
|
||||
|
||||
func TestJitsiService_DeleteMeeting(t *testing.T) {
|
||||
cfg := &config.JitsiConfig{
|
||||
BaseURL: "https://meet.jit.si",
|
||||
}
|
||||
|
||||
service := NewJitsiService(cfg)
|
||||
|
||||
// Test deleting a meeting (should not error)
|
||||
err := service.DeleteMeeting("test-room-789")
|
||||
if err != nil {
|
||||
t.Errorf("Expected no error, got %v", err)
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user