diff --git a/internal/core/booking/booking.go b/internal/core/booking/booking.go index f681aa1..0e420dd 100644 --- a/internal/core/booking/booking.go +++ b/internal/core/booking/booking.go @@ -107,7 +107,7 @@ func (a *Adapter) Book(member *discord.Member, guild *discord.Guild, spotName st } if endAt.Sub(startAt) > 3*time.Hour { - return []*reservation.Reservation{}, fmt.Errorf("reservation cannot take more than 3 hours") + return []*reservation.Reservation{}, errors.New("reservation cannot take more than 3 hours") } conflictingReservations, err := a.reservationRepo.SelectOverlappingReservations(context.Background(), spotName, startAt, endAt, guild.ID) @@ -115,12 +115,20 @@ func (a *Adapter) Book(member *discord.Member, guild *discord.Guild, spotName st return []*reservation.Reservation{}, fmt.Errorf("could not select overlapping reservations: %w", err) } + authorsConflictingReservations, _ := collections.PoorMansFind(conflictingReservations, func(r *reservation.Reservation) bool { + return r.AuthorDiscordID == member.ID + }) + + if authorsConflictingReservations != nil && overbook { + return []*reservation.Reservation{}, errors.New("you cannot overbook yourself") + } + if len(conflictingReservations) > 0 { switch canDo := overbook && isPotentiallyAbandonedReservation(conflictingReservations) || overbook && hasPermissions; canDo { case true: break case false: - return conflictingReservations, fmt.Errorf("There are conflicting reservation which prevented booking this reservation. If you would like to overbook them, ensure you have a @Postman role, then repeat the command and set 'overbook' parameter to 'true'.") + return conflictingReservations, errors.New("There are conflicting reservation which prevented booking this reservation. If you would like to overbook them, ensure you have a @Postman role, then repeat the command and set 'overbook' parameter to 'true'.") } } diff --git a/internal/core/booking/booking_test.go b/internal/core/booking/booking_test.go index ff4d5a1..3556fdb 100644 --- a/internal/core/booking/booking_test.go +++ b/internal/core/booking/booking_test.go @@ -418,3 +418,46 @@ func TestBookOnMultizoneCase(t *testing.T) { assert.Nil(err) assert.NotNil(res) } + +func TestBookFailOnOverbookAuthorsReservation(t *testing.T) { + // given + assert := assert.New(t) + guild := &discord.Guild{ + ID: "test-id", + Name: "test-guild-name", + } + member := &discord.Member{ + ID: "test-member", + Nick: "test-nick", + } + startAt := time.Now() + endAt := startAt.Add(1 * time.Hour) + spotInput := &spot.Spot{ + Name: "test-spot", + ID: 1, + CreatedAt: time.Now(), + } + conflictingReservations := []*reservation.Reservation{ + { + Author: member.Username, + CreatedAt: time.Now(), + StartAt: time.Date(1, 1, 1, 16, 0, 0, 0, time.UTC), + EndAt: time.Date(1, 1, 1, 17, 0, 0, 0, time.UTC), + SpotID: 1, + GuildID: guild.ID, + AuthorDiscordID: member.ID, + }, + } + spotService := new(mocks.MockSpotRepo) + spotService.On("SelectAllSpots", mocks.ContextMock).Return([]*spot.Spot{spotInput}, nil) + reservationService := new(mocks.MockReservationRepo) + reservationService.On("SelectOverlappingReservations", mocks.ContextMock, spotInput.Name, startAt, endAt, guild.ID).Return(conflictingReservations, nil) + adapter := NewAdapter(spotService, reservationService) + + // when + res, err := adapter.Book(member, guild, spotInput.Name, startAt, endAt, true, true) + + // assert + assert.NotNil(err) + assert.Empty(res) +} diff --git a/internal/infrastructure/bot/handlers.go b/internal/infrastructure/bot/handlers.go index f3514cf..2ea2b85 100644 --- a/internal/infrastructure/bot/handlers.go +++ b/internal/infrastructure/bot/handlers.go @@ -1,6 +1,7 @@ package bot import ( + "errors" "fmt" "strings" "time" @@ -14,8 +15,10 @@ import ( "spot-assistant/internal/core/dto/summary" ) -// System events -// Events that are sent by discord itself +/* +* +System events that are initialized by Discord. +*/ func (b *Bot) GuildCreate(s *discordgo.Session, g *discordgo.GuildCreate) { b.log.Debug("GuildCreate") @@ -29,7 +32,7 @@ func (b *Bot) Ready(s *discordgo.Session, r *discordgo.Ready) { defer b.eventHandler.OnReady(b) } -// When a slash command is invoked, this is the entry point. +// InteractionCreate this is the entry point when a slash command is invoked. func (b *Bot) InteractionCreate(s *discordgo.Session, i *discordgo.InteractionCreate) { b.log.Debug("InteractionCreate") tStart := time.Now() @@ -68,7 +71,7 @@ func (b *Bot) Book(i *discordgo.InteractionCreate) error { case 3: break default: - return fmt.Errorf("Book command requires 3 arguments") + return errors.New("book command requires 3 arguments") } startAt, err := time.Parse(stringsHelper.DC_TIME_FORMAT, i.ApplicationCommandData().Options[1].StringValue()) @@ -173,7 +176,7 @@ func (b *Bot) BookAutocomplete(i *discordgo.InteractionCreate) error { return o.Focused }) if index == -1 { - return fmt.Errorf("none of the options were selected for autocompletion") + return errors.New("none of the options were selected for autocompletion") } response, err := b.eventHandler.OnBookAutocomplete(book.BookAutocompleteRequest{ @@ -192,7 +195,7 @@ func (b *Bot) BookAutocomplete(i *discordgo.InteractionCreate) error { func (b *Bot) Unbook(i *discordgo.InteractionCreate) error { if len(i.ApplicationCommandData().Options) < 1 { - return fmt.Errorf("you must select a reservation to unbook") + return errors.New("you must select a reservation to unbook") } reservationId, err := stringsHelper.StrToInt64(i.ApplicationCommandData().Options[0].StringValue())