private static TournamentsManager CreateTournamentsManager(ulong id) { TournamentsManager manager = new TournamentsManager() { GuildId = id }; return(manager); }
protected ITournamentState AddCurrentTournament( GlobalTournamentsManager globalManager, ulong guildId = DefaultGuildId, string tournamentName = DefaultTournamentName) { TournamentsManager manager = globalManager.GetOrAdd(guildId, id => new TournamentsManager()); ITournamentState state = new TournamentState(guildId, tournamentName); state = manager.AddOrUpdateTournament(tournamentName, state, (name, oldState) => state); Assert.IsTrue( manager.TrySetCurrentTournament(tournamentName, out string errorMessage), "We should be able to set the current tournament."); return(state); }
public void TrySetCurrentTournamentWithUnaddedTournament() { TournamentsManager manager = new TournamentsManager() { GuildId = 1 }; Assert.IsFalse( manager.TrySetCurrentTournament(DefaultTournamentName, out string errorMessage), "Shouldn't be able to set current tournament with a non-existent tournament."); // TODO: If we go to using resources or string consts, check that value instead of just checking that the // message isn't null. Assert.IsNotNull(errorMessage, "Error message should not be null."); }
// TODO: Add test for ClearAll that checks that all artifacts are cleared. private static void AddTournamentDirectorDirectly(GlobalTournamentsManager globalManager, ulong userId) { TournamentsManager manager = globalManager.GetOrAdd( DefaultGuildId, id => new TournamentsManager() { GuildId = id }); ITournamentState state = manager.AddOrUpdateTournament( DefaultTournamentName, new TournamentState(DefaultGuildId, DefaultTournamentName), (name, oldState) => oldState); Assert.IsTrue(state.TryAddDirector(userId), "First TD added should occur."); }
public static async Task DoReadActionOnCurrentTournamentForMemberAsync( this TournamentsManager manager, IUser user, Func <IReadOnlyTournamentState, Task> action) { Result <Task> result = manager.TryReadActionOnCurrentTournament(action); if (result.Success) { await result.Value; return; } IDMChannel channel = await user.GetOrCreateDMChannelAsync(); await channel.SendMessageAsync(BotStrings.UnableToPerformCommand(result.ErrorMessage)); }
public void TryGetTournament() { TournamentsManager manager = new TournamentsManager() { GuildId = 1 }; Assert.IsFalse( manager.TryGetTournament(DefaultTournamentName, out ITournamentState state), "No tournament state should exist."); TournamentState newState = new TournamentState(1, DefaultTournamentName); manager.AddOrUpdateTournament(DefaultTournamentName, newState, (name, oldState) => oldState); Assert.IsTrue(manager.TryGetTournament(DefaultTournamentName, out state), "Tournament state should exist."); Assert.AreEqual(newState, state, "Wrong tournament retrieved."); }
public async Task EndTournamentAsync() { await this.DoReadWriteActionOnCurrentTournamentAsync( currentTournament => this.CleanupTournamentArtifactsAsync(currentTournament)); TournamentsManager manager = this.GlobalManager.GetOrAdd(this.Context.Guild.Id, CreateTournamentsManager); if (!manager.TryClearCurrentTournament()) { this.Logger.Debug("Tournament cleanup failed"); await this.SendChannelMessage(BotStrings.TournamentWasNotRemoved); return; } this.Logger.Debug("Tournament cleanup finished"); }
public async Task RemoveTournamentDirectorAsync(IGuildUser oldDirector, string tournamentName) { Verify.IsNotNull(oldDirector, nameof(oldDirector)); if (string.IsNullOrWhiteSpace(tournamentName)) { this.Logger.Debug("Couldn't remove director {id} for tournament with blank name", oldDirector.Id); return; } tournamentName = tournamentName.Trim(); TournamentsManager manager = this.GlobalManager.GetOrAdd(this.Context.Guild.Id, CreateTournamentsManager); // TODO: Harden this. Since it's not guaranteed to be the current tournament, we can't use the helper // methods if (!manager.TryGetTournament(tournamentName, out ITournamentState state)) { this.Logger.Debug( "Couldn't remove director {id} for nonexistent tournament {tournamentName}", oldDirector.Id, tournamentName); await this.Context.Channel.SendMessageAsync( BotStrings.TournamentDoesNotExist(tournamentName, this.Context.Guild.Name), options : RequestOptionsSettings.Default); return; } if (state.TryRemoveDirector(oldDirector.Id)) { this.Logger.Debug( "Removed {id} as a tournament director for {tournamentName}", oldDirector.Id, tournamentName); await this.Context.Channel.SendMessageAsync( BotStrings.RemovedTournamentDirector(tournamentName, this.Context.Guild.Name), options : RequestOptionsSettings.Default); return; } this.Logger.Debug( "User {id} is not a director for {tournamentName}, so could not be removed", oldDirector.Id, tournamentName); await this.Context.Channel.SendMessageAsync( BotStrings.UserNotTournamentDirector(tournamentName, this.Context.Guild.Name), options : RequestOptionsSettings.Default); }
public Task AddTournamentDirectorAsync(IGuildUser newDirector, string tournamentName) { Verify.IsNotNull(newDirector, nameof(newDirector)); if (string.IsNullOrWhiteSpace(tournamentName)) { this.Logger.Debug("Did not add {id} to tournament with blank name", newDirector.Id); return(Task.CompletedTask); } TournamentsManager manager = this.GlobalManager.GetOrAdd(this.Context.Guild.Id, CreateTournamentsManager); ITournamentState state = new TournamentState(this.Context.Guild.Id, tournamentName.Trim()); bool updateSuccessful = state.TryAddDirector(newDirector.Id); manager.AddOrUpdateTournament( tournamentName, state, (name, tournamentState) => { updateSuccessful = tournamentState.TryAddDirector(newDirector.Id); return(tournamentState); }); // TODO: Need to handle this differently depending on the stage. Completed shouldn't do anything, and // after RoleSetup we should give them the TD role. if (updateSuccessful) { this.Logger.Debug( "Added {id} as a tournament director for {tournamentName}", newDirector.Id, tournamentName); return(this.SendChannelMessage( BotStrings.AddTournamentDirectorSuccessful(tournamentName, this.Context.Guild.Name))); } this.Logger.Debug( "{id} already a tournament director for {tournamentName}", newDirector.Id, tournamentName); return(this.SendChannelMessage( BotStrings.UserAlreadyTournamentDirector(tournamentName, this.Context.Guild.Name))); }
public void AddOrUpdateTournamentAddsThenUpdates() { const string originalTournamentName = "Tournament1"; const string updatedTournamentName = "Tournament2"; TournamentsManager manager = new TournamentsManager() { GuildId = 1 }; TournamentState originalState = new TournamentState(1, originalTournamentName); string[] expectedNames = new string[] { originalTournamentName, updatedTournamentName }; for (int i = 0; i < expectedNames.Length; i++) { ITournamentState state = manager.AddOrUpdateTournament( originalTournamentName, originalState, (name, oldState) => new TournamentState(1, updatedTournamentName)); Assert.AreEqual( expectedNames[i], state.Name, $"Unexpected tournament returned after {i + 1} call(s)."); } }
public async Task AddTournamentDirector() { MessageStore messageStore = new MessageStore(); ICommandContext context = this.CreateCommandContext(messageStore); GlobalTournamentsManager globalManager = new GlobalTournamentsManager(); BotCommandHandler commandHandler = new BotCommandHandler(context, globalManager); IGuildUser guildUser = this.CreateGuildUser(DefaultUserId); await commandHandler.AddTournamentDirectorAsync(guildUser, DefaultTournamentName); string expectedMessage = BotStrings.AddTournamentDirectorSuccessful( DefaultTournamentName, DefaultGuildName); messageStore.VerifyChannelMessages(expectedMessage); TournamentsManager manager = globalManager.GetOrAdd(DefaultGuildId, id => new TournamentsManager()); Assert.IsTrue( manager.TryGetTournament(DefaultTournamentName, out ITournamentState state), "Could not find tournament."); Assert.IsTrue(state.IsDirector(DefaultUserId), "Director was not added."); }
public async Task RemoveNonexistentTournamentDirector() { const ulong otherId = DefaultUserId + 1; MessageStore messageStore = new MessageStore(); ICommandContext context = this.CreateCommandContext(messageStore); GlobalTournamentsManager globalManager = new GlobalTournamentsManager(); BotCommandHandler commandHandler = new BotCommandHandler(context, globalManager); AddTournamentDirectorDirectly(globalManager, DefaultUserId); IGuildUser guildUser = this.CreateGuildUser(otherId); await commandHandler.RemoveTournamentDirectorAsync(guildUser, DefaultTournamentName); string expectedMessage = BotStrings.UserNotTournamentDirector(DefaultTournamentName, DefaultGuildName); messageStore.VerifyChannelMessages(expectedMessage); TournamentsManager manager = globalManager.GetOrAdd(DefaultGuildId, id => new TournamentsManager()); Assert.IsTrue( manager.TryGetTournament(DefaultTournamentName, out ITournamentState state), "Could not find tournament."); Assert.IsFalse(state.IsDirector(otherId), "Director should not have been added."); }
public void TryClearCurrentTournament() { const string otherTournamentName = DefaultTournamentName + "2"; TournamentsManager manager = new TournamentsManager() { GuildId = 1 }; TournamentState state = new TournamentState(1, DefaultTournamentName); TournamentState otherState = new TournamentState(1, otherTournamentName); manager.AddOrUpdateTournament(DefaultTournamentName, state, (name, oldState) => oldState); manager.AddOrUpdateTournament(otherTournamentName, otherState, (name, oldState) => oldState); Assert.IsTrue( manager.TrySetCurrentTournament(DefaultTournamentName, out string errorMessage), "Couldn't set current tournament initially."); Assert.IsTrue(manager.TryClearCurrentTournament(), "Couldn't clear current tournament."); // TrySetCurrentTournament should work again if we've just cleared it. Assert.IsTrue( manager.TrySetCurrentTournament(otherTournamentName, out errorMessage), "Couldn't set current tournament after clearing it."); }
public void TrySetCurrentTournamentWhenCurrentTournamentAlreadyExists() { const string otherTournamentName = DefaultTournamentName + "2"; TournamentsManager manager = new TournamentsManager() { GuildId = 1 }; TournamentState state = new TournamentState(1, DefaultTournamentName); TournamentState otherState = new TournamentState(1, otherTournamentName); manager.AddOrUpdateTournament(DefaultTournamentName, state, (name, oldState) => oldState); manager.AddOrUpdateTournament(otherTournamentName, otherState, (name, oldState) => oldState); Assert.IsTrue( manager.TrySetCurrentTournament(DefaultTournamentName, out string errorMessage), "First TrySet should succeed."); Assert.IsFalse( manager.TrySetCurrentTournament(otherTournamentName, out errorMessage), "Shouldn't be able to set the current tournament when one is already set."); // TODO: If we go to using resources or string consts, check that value instead of just checking that the // message isn't null. Assert.IsNotNull(errorMessage, "Error message should not be null."); }
public async Task SimplestSchedule() { const string readerName = "#Reader"; const string firstTeamName = "#TeamA"; const string secondTeamName = "#TeamB"; MessageStore messageStore = new MessageStore(); ICommandContext context = this.CreateCommandContext(messageStore, guildId: DefaultGuildId); GlobalTournamentsManager globalManager = new GlobalTournamentsManager(); TournamentsManager manager = globalManager.GetOrAdd(DefaultGuildId, id => new TournamentsManager()); HashSet <Team> teams = new HashSet <Team>() { new Team() { Name = firstTeamName }, new Team() { Name = secondTeamName } }; HashSet <Reader> readers = new HashSet <Reader>() { new Reader() { Id = 0, Name = readerName } }; RoundRobinScheduleFactory factory = new RoundRobinScheduleFactory(2, 0); Schedule schedule = factory.Generate(teams, readers); ITournamentState state = new TournamentState(DefaultGuildId, "T"); state.Schedule = schedule; manager.AddOrUpdateTournament(state.Name, state, (name, oldState) => oldState); Assert.IsTrue( manager.TrySetCurrentTournament(state.Name, out string errorMessage), $"Failed to set the tournament: '{errorMessage}'"); globalManager.GetOrAdd(DefaultGuildId, id => manager); BotCommandHandler commandHandler = new BotCommandHandler(context, globalManager); await commandHandler.GetScheduleAsync(); Assert.AreEqual(1, messageStore.ChannelEmbeds.Count, "Unexpected number of embeds"); string embed = messageStore.ChannelEmbeds[0]; for (int round = 0; round < schedule.Rounds.Count; round++) { Assert.IsTrue( embed.Contains(BotStrings.RoundNumber(round + 1)), $"Round {round + 1} not found in embed. Embed: '{embed}'"); string expectedGame = BotStrings.ScheduleLine( readerName, schedule.Rounds[round].Games[0].Teams.Select(team => team.Name).ToArray()); Assert.IsTrue( embed.Contains(expectedGame), $"Game '{expectedGame}' not foudn in embed. Embed: '{embed}'"); } }
// TODO: Test these methods internal async Task HandleOnMessageReceived(SocketMessage message) { if (message.Content.TrimStart().StartsWith("!", StringComparison.InvariantCulture)) { // Ignore commands this.Logger.Verbose("Message ignored because it's a command"); return; } if (message.Author.Id == this.Client.CurrentUser.Id) { this.Logger.Verbose("Message ignored because it's from the bot"); return; } // Only pay attention to messages in a guild channel if (!(message is IUserMessage userMessage && userMessage.Channel is IGuildChannel guildChannel && userMessage.Author is IGuildUser guildUser)) { this.Logger.Verbose("Message ignored because it's not in a guild"); return; } // TODO: See if there's a cheaper way to check if we have a current tournament without making it public. // This is a read-only lock, so it shouldn't be too bad, but it'll be blocked during write operations. // Don't use the helper method because we don't want to message the user each time if there's no tournament. // We also want to split this check and the TD check because we don't need to get the Discord member for // this check. TournamentsManager manager = this.GetTournamentsManager(guildChannel.Guild); Result <bool> currentTournamentExists = manager.TryReadActionOnCurrentTournament(currentTournament => true); if (!currentTournamentExists.Success) { this.Logger.Verbose("Message ignored because no current tournament is running"); return; } // We want to do the basic access checks before using the more expensive write lock. Result <bool> hasDirectorPrivileges = manager.TryReadActionOnCurrentTournament( currentTournament => HasTournamentDirectorPrivileges(currentTournament, guildChannel, guildUser)); if (!(hasDirectorPrivileges.Success && hasDirectorPrivileges.Value)) { this.Logger.Verbose( "Message ignored because user {id} does not have tournament director privileges", guildUser.Id); return; } await manager.DoReadWriteActionOnCurrentTournamentForMemberAsync( guildUser, async currentTournament => { // TODO: We need to have locks on these. Need to check the stages between locks, and ignore the message if // it changes. // Issue is that we should really rely on a command for this case. Wouldn't quite work with locks. // But that would be something like !start, which would stop the rest of the interaction. switch (currentTournament.Stage) { case TournamentStage.AddReaders: await this.HandleAddReadersStage(currentTournament, guildChannel.Guild, message); break; case TournamentStage.SetRoundRobins: await this.HandleSetRoundRobinsStage(currentTournament, message); break; case TournamentStage.AddTeams: await this.HandleAddTeamsStage(currentTournament, message); break; case TournamentStage.Rebracketing: await this.HandleRebracketingStage(currentTournament, guildChannel.Guild, message); break; default: this.Logger.Verbose( "Message ignored because it is during the stage {stage}", currentTournament.Stage); break; } }); }
private Task DoReadActionOnCurrentTournamentAsync(Func <IReadOnlyTournamentState, Task> action) { TournamentsManager manager = this.GlobalManager.GetOrAdd(this.Context.Guild.Id, CreateTournamentsManager); return(manager.DoReadActionOnCurrentTournamentForMemberAsync(this.Context.User, action)); }
static async Task Main(string[] args) { var tournamentManager = new TournamentsManager(); await tournamentManager.StartTournamentManager(); }