* Normalize paths

* Update workflows

* Update workflows

* Update workflows

* Update csproj build dirs

* Update csproj build dirs

* Fix bugs

* Directories are hard

* Dont zip zip

* Rename to support CS#

* Fix nightly file name

* Debug

* Try referencing class directly

* Update dependabot

* Update gitignore and command tests

* More tests:

* Basic gang creation working
This commit is contained in:
Isaac
2024-09-02 21:00:25 -07:00
committed by GitHub
parent 00f0190128
commit 11ffe34855
32 changed files with 497 additions and 75 deletions

View File

@@ -9,3 +9,10 @@ updates:
directory: "/" # Location of package manifests
schedule:
interval: "weekly"
target-branch: dev
- package-ecosystem: "nuget" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "weekly"
target-branch: main
open-pull-requests-limit: 0

View File

@@ -14,8 +14,18 @@ name: "CodeQL"
on:
push:
branches: [ "main", "dev" ]
paths:
- 'Gangs*/**'
- '.github/workflows/nightly.yml'
- 'Core/**'
- 'Commands/**'
pull_request:
branches: [ "main" ]
paths:
- 'Gangs*/**'
- '.github/workflows/nightly.yml'
- 'Core/**'
- 'Commands/**'
schedule:
- cron: '34 12 * * 6'
@@ -43,8 +53,8 @@ jobs:
fail-fast: false
matrix:
include:
- language: csharp
build-mode: none
- language: csharp
build-mode: none
# CodeQL supports the following values keywords for 'language': 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift'
# Use `c-cpp` to analyze code written in C, C++ or both
# Use 'java-kotlin' to analyze code written in Java, Kotlin or both
@@ -54,23 +64,23 @@ jobs:
# If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Checkout repository
uses: actions/checkout@v4
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
# queries: security-extended,security-and-quality
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
# queries: security-extended,security-and-quality
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
with:
category: "/language:${{matrix.language}}"
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
with:
category: "/language:${{matrix.language}}"

View File

@@ -4,11 +4,15 @@ on:
push:
paths:
- 'Gangs*/**'
- '.github/workflows/dotnet.yml'
- '.github/workflows/nightly.yml'
- 'Core/**'
- 'Commands/**'
pull_request:
paths:
- 'Gangs*/**'
- '.github/workflows/dotnet.yml'
- '.github/workflows/nightly.yml'
- 'Core/**'
- 'Commands/**'
jobs:
build:

View File

@@ -11,6 +11,9 @@ on:
paths:
- 'Gangs*/**'
- '.github/workflows/nightly.yml'
- '.github/workflows/release.yml'
- 'Core/**'
- 'Commands/**'
jobs:
build:
@@ -29,8 +32,8 @@ jobs:
- run: |
dotnet restore
dotnet build Core/Core.csproj --no-restore
dotnet publish Core/Core.csproj --no-build --no-restore
dotnet build Gangs/Gangs.csproj --no-restore
dotnet publish Gangs/Gangs.csproj --no-build --no-restore
- uses: actions/upload-artifact@v4.0.0
with:

View File

@@ -30,7 +30,7 @@ jobs:
- uses: actions/upload-artifact@v4.0.0
with:
name: gangs
path: build/Gangs
path: build/
# If build didn't put any artifacts in the build folder, consider it an error
if-no-files-found: error

6
.gitignore vendored
View File

@@ -2,4 +2,8 @@ bin/
obj/
/packages/
riderModule.iml
/_ReSharper.Caches/
/_ReSharper.Caches/
Gangs.zip
build
.idea/
*.DotSettings.user

View File

@@ -11,10 +11,9 @@ namespace Commands;
public class CommandManager(IGangManager gangMgr)
: MockCommandManager, IPluginBehavior {
private BasePlugin plugin = null!;
private BasePlugin? plugin;
public void Start(BasePlugin? basePlugin, bool hotReload) {
ArgumentNullException.ThrowIfNull(basePlugin, nameof(basePlugin));
plugin = basePlugin;
RegisterCommand(new GangCommand(gangMgr));
@@ -22,7 +21,7 @@ public class CommandManager(IGangManager gangMgr)
public override bool RegisterCommand(ICommand command) {
base.RegisterCommand(command);
plugin.AddCommand(command.Name, command.Description ?? string.Empty,
plugin?.AddCommand(command.Name, command.Description ?? string.Empty,
(player, info) => {
var wrapper = player == null ? null : new PlayerWrapper(player);
var args = info.GetCommandString.Split(" ");

View File

@@ -25,18 +25,19 @@ public class GangCommand(IGangManager gangMgr) : ICommand {
public async Task<CommandResult> Execute(PlayerWrapper? executor,
CommandInfoWrapper info) {
if (info.ArgCount == 0 || info[0] != Name) {
if (info.ArgCount == 0)
throw new InvalidOperationException(
"Attempted to execute GangCommand with no arguments");
if (info.ArgCount == 0)
throw new InvalidOperationException(
"Attempted to execute GangCommand with no arguments");
if (info[0] != Name)
throw new InvalidOperationException(
$"Attempted to execute GangCommand with invalid name: {info[0]}");
}
if (info.ArgCount == 1) return CommandResult.INVALID_ARGS;
if (!sub.TryGetValue(info[1], out var command)) {
// print usage
// info.ReplySync("Usage: /css_gang [create|help]");
return CommandResult.UNKNOWN_COMMAND;
}

View File

@@ -11,6 +11,7 @@ namespace Commands.gang;
public class CreateCommand(IGangManager gang) : ICommand {
public string Name => "create";
public string? Description => "Creates a new gang";
public string Usage => "[name]";
public async Task<CommandResult> Execute(PlayerWrapper? executor,
CommandInfoWrapper info) {
@@ -18,7 +19,7 @@ public class CreateCommand(IGangManager gang) : ICommand {
if (info.ArgCount < 2) {
info.ReplySync("Please provide a name for the gang");
return CommandResult.FAILURE;
return CommandResult.INVALID_ARGS;
}
var name = string.Join(' ', info.ArgString.Split(" ").Skip(1));
@@ -33,6 +34,14 @@ public class CreateCommand(IGangManager gang) : ICommand {
return CommandResult.FAILURE;
}
throw new NotImplementedException();
var newGang = await gang.CreateGang(name, executor.Steam);
if (newGang == null) {
info.ReplySync("Failed to create gang");
return CommandResult.FAILURE;
}
info.ReplySync($"Gang '{name}' (#{newGang.GangId}) created successfully");
return CommandResult.SUCCESS;
}
}

View File

@@ -2,7 +2,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GangsAPI", "GangsAPI\GangsAPI.csproj", "{787D12D8-1310-4042-ADCE-102E517F269E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Core", "Core\Core.csproj", "{3EA38296-9022-4874-8309-872388D884DE}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Gangs", "Gangs\Gangs.csproj", "{3EA38296-9022-4874-8309-872388D884DE}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GangsTest", "GangsTest\GangsTest.csproj", "{B1D1E7C7-BDF3-4238-9025-4FEB2B7DAB89}"
EndProject

View File

@@ -19,6 +19,9 @@ public class CS2Gangs(IServiceProvider provider) : BasePlugin, IGangPlugin {
var extensions = scope.ServiceProvider.GetServices<IPluginBehavior>()
.ToImmutableList();
Logger.LogInformation("[Gangs] Loading {Count} extensions",
extensions.Count);
foreach (var ext in extensions) {
RegisterAllAttributes(ext);
try {

View File

@@ -8,7 +8,7 @@ using Mock;
namespace GangsImpl;
public class GangServiceCollection : IPluginServiceCollection<IGangPlugin> {
public class GangServiceCollection : IPluginServiceCollection<CS2Gangs> {
public void ConfigureServices(IServiceCollection serviceCollection) {
serviceCollection.AddPluginBehavior<IGangManager, MockGangManager>();
serviceCollection.AddPluginBehavior<IPlayerManager, MockPlayerManager>();

View File

@@ -17,9 +17,22 @@
<PackageReference Include="xunit" Version="2.9.0"/>
</ItemGroup>
<ItemGroup>
<Compile Remove="build\**" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Remove="build\**" />
</ItemGroup>
<ItemGroup>
<None Remove="build\**" />
<None Remove="Gangs.zip" />
</ItemGroup>
<PropertyGroup>
<!--Publish Configuration-->
<PublishBaseDirectory>$(MSBuildThisFileDirectory)/../../build</PublishBaseDirectory>
<PublishBaseDirectory>$(MSBuildThisFileDirectory)/../build</PublishBaseDirectory>
<PublishDir>$(PublishBaseDirectory)/Gangs</PublishDir>
<PublishRelease>false</PublishRelease>

View File

@@ -1,10 +1,38 @@
namespace GangsAPI.Data.Command;
public enum CommandResult {
/// <summary>
/// The command completed successfully
/// </summary>
SUCCESS,
/// <summary>
/// The command encountered an error or other
/// scenario that prevented success
/// </summary>
FAILURE,
/// <summary>
/// The command was improperly formatted
/// </summary>
UNKNOWN_COMMAND,
/// <summary>
/// The command has improper arguments, or
/// no sufficient arguments
/// </summary>
INVALID_ARGS,
/// <summary>
/// The executor of the command did not have
/// the required permissions
/// </summary>
NO_PERMISSION,
/// <summary>
/// This command can only be executed by a player
/// (i.e. not from the console)
/// </summary>
PLAYER_ONLY
}

View File

@@ -67,12 +67,14 @@ public interface IGangRank {
/// <summary>
/// The member has full access to all permissions.
/// </summary>
ADMINISTRATOR = 1 << 10,
ADMINISTRATOR = 1 << 10 | INVITE_OTHERS | KICK_OTHERS | BANK_DEPOSIT
| BANK_WITHDRAW | PROMOTE_OTHERS | DEMOTE_OTHERS | PURCHASE_PERKS
| MANAGE_PERKS | MANAGE_RANKS | CREATE_RANKS,
/// <summary>
/// The member is the owner of the gang, and can not be kicked.
/// </summary>
OWNER = 1 << 11
OWNER = 1 << 11 | ADMINISTRATOR
}
string Name { get; }

View File

@@ -5,7 +5,8 @@ namespace GangsAPI.Services.Commands;
public interface ICommand : IPluginBehavior {
string Name { get; }
string? Description { get; }
string? Description => null;
string Usage => "";
string[] RequiredFlags => [];
string[] RequiredGroups => [];

View File

@@ -24,11 +24,15 @@ public class MockCommandManager : ICommandManager {
if (!command.CanExecute(executor)) return CommandResult.NO_PERMISSION;
var result = CommandResult.FAILURE;
var info = new CommandInfoWrapper(executor, args: args);
await Task.Run(async () => {
result = await command.Execute(executor,
new CommandInfoWrapper(executor, args: args));
result = await command.Execute(executor, info);
});
if (result == CommandResult.PLAYER_ONLY)
info.ReplySync("This command can only be executed by a player");
return result;
}
}

View File

@@ -13,8 +13,10 @@ public class MockGang : IGang {
Stats = new HashSet<IStat>();
Ranks = new HashSet<IGangRank>();
Members.Add(owner,
new MockGangRank(0, "Owner", IGangRank.Permissions.OWNER));
var ownerRank = new MockGangRank(0, "Owner", IGangRank.Permissions.OWNER);
Members.Add(owner, ownerRank);
Ranks.Add(ownerRank);
}
public int GangId { get; }

View File

@@ -4,23 +4,23 @@ using GangsAPI.Services;
namespace Mock;
public class MockGangManager : IGangManager {
private readonly HashSet<IGang> gangs = [];
private readonly HashSet<IGang> cachedGangs = [], backendGangs = [];
public Task<IEnumerable<IGang>> GetGangs() {
return Task.FromResult(gangs.AsEnumerable());
return Task.FromResult(cachedGangs.AsEnumerable());
}
public Task<IGang?> GetGang(int id) {
return Task.FromResult(gangs.FirstOrDefault(g => g.GangId == id));
return Task.FromResult(cachedGangs.FirstOrDefault(g => g.GangId == id));
}
public Task<IGang?> GetGang(ulong steam) {
return Task.FromResult(
gangs.FirstOrDefault(g => g.Members.ContainsKey(steam)));
cachedGangs.FirstOrDefault(g => g.Members.ContainsKey(steam)));
}
public Task<bool> UpdateGang(IGang gang) {
var g = gangs.FirstOrDefault(g => g.GangId == gang.GangId);
var g = cachedGangs.FirstOrDefault(g => g.GangId == gang.GangId);
if (g == null) return Task.FromResult(false);
g.Name = gang.Name;
g.Members.Clear();
@@ -29,16 +29,23 @@ public class MockGangManager : IGangManager {
}
public Task<bool> DeleteGang(int id) {
return Task.FromResult(gangs.RemoveWhere(g => g.GangId == id) > 0);
return Task.FromResult(cachedGangs.RemoveWhere(g => g.GangId == id) > 0);
}
public Task<IGang?> CreateGang(string name, ulong owner) {
var id = gangs.Count + 1;
var id = cachedGangs.Count + 1;
var gang = new MockGang(id, name, owner);
return Task.FromResult((IGang?)(gangs.Add(gang) ? gang.Clone() : null));
if (cachedGangs.Any(g => g.GangId == id))
return Task.FromResult<IGang?>(null);
cachedGangs.Add(gang);
backendGangs.Add(gang);
return Task.FromResult(gang.Clone() as IGang);
}
public void ClearCache() { gangs.Clear(); }
public void ClearCache() { cachedGangs.Clear(); }
public Task Load() { return Task.CompletedTask; }
public Task Load() {
cachedGangs.UnionWith(backendGangs);
return Task.CompletedTask;
}
}

View File

@@ -2,7 +2,7 @@ using GangsAPI.Data.Stat;
namespace Mock;
public class MockStat(string statId, string name, string? desc) : IStat {
public class MockStat(string statId, string name, string? desc = null) : IStat {
public string StatId { get; } = statId;
public string Name { get; } = name;
public string? Description { get; } = desc;
@@ -31,6 +31,5 @@ public class MockGangStat<T>(string statId, string name, string? desc, T value)
public MockGangStat(IStat b, T value) : this(b.StatId, b.Name, b.Description,
value) { }
public int Key { get; init; }
public T Value { get; set; } = value;
}

View File

@@ -0,0 +1,26 @@
using System.Collections;
using Commands;
using Commands.gang;
using GangsAPI;
using GangsAPI.Services;
using Mock;
namespace GangsTest.Commands;
public class CommandTestData : IEnumerable<object[]> {
private static readonly IGangManager manager = new MockGangManager();
private readonly IBehavior[] behaviors = [
new CreateCommand(manager), new HelpCommand(), new GangCommand(manager)
];
public CommandTestData() {
foreach (var behavior in behaviors) behavior.Start();
}
public IEnumerator<object[]> GetEnumerator() {
return behaviors.Select(behavior => (object[]) [behavior]).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }
}

View File

@@ -14,15 +14,81 @@ public class CreateTests(ICommandManager commands, IGangManager gangMgr)
"Test Player");
[Fact]
public async Task Create_TestNonPlayer() {
public async Task Create_NonPlayer() {
Assert.Equal(CommandResult.PLAYER_ONLY,
await Commands.ProcessCommand(null, "create"));
}
[Fact]
public async Task Create_TestNoName() {
Assert.Equal(CommandResult.FAILURE,
public async Task Create_NoName() {
Assert.Equal(CommandResult.INVALID_ARGS,
await Commands.ProcessCommand(player, "create"));
Assert.Contains("Please provide a name for the gang", player.ConsoleOutput);
}
[Fact]
public async Task Create_Simple() {
var gang = await gangMgr.GetGang(player.Steam);
Assert.Null(gang);
Assert.Equal(CommandResult.SUCCESS,
await Commands.ProcessCommand(player, "create", "foobar"));
gang = await gangMgr.GetGang(player.Steam);
Assert.NotNull(gang);
Assert.Equal("foobar", gang.Name);
Assert.Contains($"Gang 'foobar' (#{gang.GangId}) created successfully",
player.ConsoleOutput);
}
[Fact]
public async Task Create_MultiWord() {
var gang = await gangMgr.GetGang(player.Steam);
Assert.Null(gang);
Assert.Equal(CommandResult.SUCCESS,
await Commands.ProcessCommand(player, "create", "foo bar"));
gang = await gangMgr.GetGang(player.Steam);
Assert.NotNull(gang);
Assert.Equal("foo bar", gang.Name);
}
[Fact]
public async Task Create_MultiParam() {
var gang = await gangMgr.GetGang(player.Steam);
Assert.Null(gang);
Assert.Equal(CommandResult.SUCCESS,
await Commands.ProcessCommand(player, "create", "foo bar", "baz"));
gang = await gangMgr.GetGang(player.Steam);
Assert.NotNull(gang);
Assert.Equal("foo bar baz", gang.Name);
}
[Fact]
public async Task Create_Already_Ganged() {
Assert.Equal(CommandResult.SUCCESS,
await Commands.ProcessCommand(player, "create", "foo bar"));
Assert.Equal(CommandResult.FAILURE,
await Commands.ProcessCommand(player, "create", "bar foo"));
Assert.Contains("You are already in a gang", player.ConsoleOutput);
}
[Fact]
public async Task Create_Already_Ganged_Uncached() {
Assert.Equal(CommandResult.SUCCESS,
await Commands.ProcessCommand(player, "create", "foo bar"));
gangMgr.ClearCache();
await gangMgr.Load();
Assert.Equal(CommandResult.FAILURE,
await Commands.ProcessCommand(player, "create", "bar foo"));
Assert.Contains("You are already in a gang", player.ConsoleOutput);
}
[Fact]
public async Task Create_Duplicate_Name() {
var other =
new PlayerWrapper((ulong)new Random().NextInt64(), "Other Player");
Assert.Equal(CommandResult.SUCCESS,
await Commands.ProcessCommand(player, "create", "foo bar"));
Assert.Equal(CommandResult.FAILURE,
await Commands.ProcessCommand(other, "create", "foo bar"));
Assert.Contains("Gang 'foo bar' already exists", other.ConsoleOutput);
}
}

View File

@@ -0,0 +1,13 @@
using GangsAPI.Services.Commands;
namespace GangsTest.Commands;
public class FieldTests {
[Theory]
[ClassData(typeof(CommandTestData))]
public void Command_Fields(ICommand cmd) {
Assert.NotEmpty(cmd.Name);
Assert.False(cmd.Usage.StartsWith(cmd.Name),
"Command usage should not start with the command name");
}
}

View File

@@ -14,6 +14,21 @@ public class GangCommandTests(ICommandManager commands, IGangManager gangMgr)
await Commands.ProcessCommand(TestPlayer, Command.Name));
}
[Fact]
public async Task Gang_TestInvalid_Name() {
await Assert.ThrowsAnyAsync<InvalidOperationException>(async () => {
await Command.Execute(TestPlayer,
new CommandInfoWrapper(TestPlayer, 0, "foobar"));
});
}
[Fact]
public async Task Gang_TestInvalid_Null() {
await Assert.ThrowsAnyAsync<InvalidOperationException>(async () => {
await Command.Execute(TestPlayer, new CommandInfoWrapper(TestPlayer, 0));
});
}
[Fact]
public async Task Gang_TestUnknown() {
Assert.Equal(CommandResult.UNKNOWN_COMMAND,

View File

@@ -1,11 +1,14 @@
using System.Collections;
using Commands;
using GangsAPI;
using Mock;
namespace GangsTest.Commands.ManagerTests;
public class ManagerData : IEnumerable<object[]> {
private readonly IBehavior[] behaviors = [new MockCommandManager()];
private readonly IBehavior[] behaviors = [
new MockCommandManager(), new CommandManager(new MockGangManager())
];
public ManagerData() {
foreach (var behavior in behaviors) behavior.Start();

View File

@@ -0,0 +1,26 @@
using GangsAPI.Data;
using GangsAPI.Data.Command;
using GangsAPI.Services.Commands;
namespace GangsTest.Commands.ManagerTests;
public class ManagerHandling : ManagerTests {
private class PlayerOnlyCommand : ICommand {
public string Name => "css_player";
public Task<CommandResult> Execute(PlayerWrapper? executor,
CommandInfoWrapper info) {
return Task.FromResult(CommandResult.PLAYER_ONLY);
}
}
[Theory]
[ClassData(typeof(ManagerData))]
public async Task Command_PlayerOnly(ICommandManager mgr) {
mgr.RegisterCommand(new PlayerOnlyCommand());
Assert.Equal(CommandResult.PLAYER_ONLY,
await mgr.ProcessCommand(TestPlayer, "css_player"));
Assert.Contains("This command can only be executed by a player",
TestPlayer.ConsoleOutput);
}
}

View File

@@ -1,4 +1,6 @@
using GangsAPI.Services;
using GangsAPI.Data.Gang;
using GangsAPI.Services;
using Mock;
namespace GangsTest.GangTests;
@@ -11,6 +13,7 @@ public class GangCreationTests(IPlayerManager playerMgr) {
Assert.Equal("foobar", dummy.Name);
Assert.Equal((ulong)0, dummy.Owner);
Assert.Single(dummy.Members);
Assert.Single(dummy.Ranks);
}
[Theory]
@@ -22,20 +25,76 @@ public class GangCreationTests(IPlayerManager playerMgr) {
Assert.Equal("foobar", dummy.Name);
Assert.Equal((ulong)0, dummy.Owner);
Assert.Single(dummy.Members);
Assert.Single(dummy.Ranks);
}
[Theory]
[ClassData(typeof(GangManagerData))]
public async Task Gang_Clone(IGangManager mgr) {
var dummy = await mgr.CreateGang("foobar", 0);
Assert.NotNull(dummy);
var clone = dummy.Clone() as IGang;
Assert.NotNull(clone);
Assert.Equivalent(dummy, clone, true);
Assert.NotSame(dummy, clone);
Assert.NotSame(dummy.Members, clone.Members);
Assert.NotSame(dummy.Ranks, clone.Ranks);
Assert.NotSame(dummy.Perks, clone.Perks);
Assert.NotSame(dummy.Stats, clone.Stats);
}
[Theory]
[ClassData(typeof(GangManagerData))]
public async Task Gang_Clone_WithPerks(IGangManager mgr) {
var dummy = await mgr.CreateGang("foobar", 0);
Assert.NotNull(dummy);
dummy.Perks.Add(new MockStat("test_perk", "Test Perk"));
var clone = dummy.Clone() as IGang;
Assert.NotNull(clone);
Assert.Equivalent(dummy, clone, true);
Assert.NotSame(dummy, clone);
Assert.NotSame(dummy.Members, clone.Members);
Assert.NotSame(dummy.Ranks, clone.Ranks);
Assert.NotSame(dummy.Perks, clone.Perks);
Assert.NotSame(dummy.Stats, clone.Stats);
}
[Theory]
[ClassData(typeof(GangManagerData))]
public async Task Gang_Clone_WithStats(IGangManager mgr) {
var dummy = await mgr.CreateGang("foobar", 0);
Assert.NotNull(dummy);
dummy.Stats.Add(new MockStat("test_stats", "Test Stat"));
var clone = dummy.Clone() as IGang;
Assert.NotNull(clone);
Assert.Equivalent(dummy, clone, true);
Assert.NotSame(dummy, clone);
Assert.NotSame(dummy.Members, clone.Members);
Assert.NotSame(dummy.Ranks, clone.Ranks);
Assert.NotSame(dummy.Perks, clone.Perks);
Assert.NotSame(dummy.Stats, clone.Stats);
}
[Theory]
[ClassData(typeof(GangManagerData))]
public async Task Gang_CreateMultiple(IGangManager mgr) {
var dummy1 = await mgr.CreateGang("foobar", 0);
var steam1 = (ulong)new Random().NextInt64();
var steam2 = (ulong)new Random().NextInt64();
var dummy1 = await mgr.CreateGang("foobar", steam1);
Assert.NotNull(dummy1);
Assert.Equal("foobar", dummy1.Name);
Assert.Equal((ulong)0, dummy1.Owner);
Assert.Equal(steam1, dummy1.Owner);
Assert.Single(dummy1.Members);
var dummy2 = await mgr.CreateGang("barfoo", 0);
var dummy2 = await mgr.CreateGang("barfoo", steam2);
Assert.NotNull(dummy2);
Assert.Equal("barfoo", dummy2.Name);
Assert.Equal((ulong)0, dummy2.Owner);
Assert.Equal(steam2, dummy2.Owner);
Assert.Single(dummy2.Members);
Assert.NotSame(dummy1, dummy2);
Assert.NotEqual(dummy1.GangId, dummy2.GangId);
@@ -44,11 +103,19 @@ public class GangCreationTests(IPlayerManager playerMgr) {
[Theory]
[ClassData(typeof(GangManagerData))]
public async Task Gang_CreateMultipleFromGangPlayer(IGangManager mgr) {
var player = await playerMgr.CreatePlayer(0);
var dummy = await mgr.CreateGang("foobar", player);
Assert.NotNull(dummy);
Assert.Equal("foobar", dummy.Name);
Assert.Equal((ulong)0, dummy.Owner);
Assert.Single(dummy.Members);
var player1 = await playerMgr.CreatePlayer((ulong)new Random().NextInt64());
var player2 = await playerMgr.CreatePlayer((ulong)new Random().NextInt64());
var dummy1 = await mgr.CreateGang("foobar", player1);
var dummy2 = await mgr.CreateGang("barfoo", player2);
Assert.NotNull(dummy1);
Assert.NotNull(dummy2);
Assert.Equal("foobar", dummy1.Name);
Assert.Equal(player1.Steam, dummy1.Owner);
Assert.Single(dummy1.Members);
Assert.Equal("barfoo", dummy2.Name);
Assert.Equal(player2.Steam, dummy2.Owner);
Assert.Single(dummy2.Members);
Assert.NotSame(dummy1, dummy2);
Assert.NotEqual(dummy1.GangId, dummy2.GangId);
}
}

View File

@@ -0,0 +1,76 @@
using GangsAPI.Data.Gang;
using GangsAPI.Services;
using Mock;
namespace GangsTest.GangTests;
public class GangFetchTests(IPlayerManager playerMgr) {
[Theory]
[ClassData(typeof(GangManagerData))]
public async Task Gang_Create(IGangManager mgr) {
Assert.Empty(await mgr.GetGangs());
var dummy = await mgr.CreateGang("foobar", 0);
var gangs = (await mgr.GetGangs()).ToHashSet();
Assert.NotNull(dummy);
Assert.NotNull(gangs);
Assert.Single(gangs);
Assert.Equal(dummy, gangs.First());
}
[Theory]
[ClassData(typeof(GangManagerData))]
public async Task Gang_CreateFromGangPlayer(IGangManager mgr) {
Assert.Empty(await mgr.GetGangs());
var player = await playerMgr.CreatePlayer(0);
var dummy = await mgr.CreateGang("foobar", player);
var gangs = (await mgr.GetGangs()).ToHashSet();
Assert.NotNull(dummy);
Assert.NotNull(gangs);
Assert.Single(gangs);
Assert.Equal(dummy, gangs.First());
}
[Theory]
[ClassData(typeof(GangManagerData))]
public async Task Gang_Clone(IGangManager mgr) {
var dummy = await mgr.CreateGang("foobar", 0);
Assert.NotNull(dummy);
var clone = dummy.Clone() as IGang;
Assert.Single(await mgr.GetGangs());
}
[Theory]
[ClassData(typeof(GangManagerData))]
public async Task Gang_CreateMultiple(IGangManager mgr) {
var dummy1 = await mgr.CreateGang("foobar", 0);
Assert.NotNull(dummy1);
var dummy2 = await mgr.CreateGang("barfoo", 1);
Assert.NotNull(dummy2);
Assert.NotSame(dummy1, dummy2);
var gangs = (await mgr.GetGangs()).ToHashSet();
Assert.NotNull(gangs);
Assert.Equal(2, gangs.Count);
Assert.Contains(gangs, g => g.GangId == dummy1.GangId);
Assert.Contains(gangs, g => g.GangId == dummy2.GangId);
}
[Theory]
[ClassData(typeof(GangManagerData))]
public async Task Gang_FetchId(IGangManager mgr) {
var dummy = await mgr.CreateGang("foobar", (ulong)new Random().NextInt64());
Assert.NotNull(dummy);
var gang = await mgr.GetGang(dummy.GangId);
Assert.NotNull(gang);
Assert.Equal(dummy, gang);
}
[Theory]
[ClassData(typeof(GangManagerData))]
public async Task Gang_FetchSteam(IGangManager mgr) {
var dummy = await mgr.CreateGang("foobar", (ulong)new Random().NextInt64());
Assert.NotNull(dummy);
var gang = await mgr.GetGang(dummy.Owner);
Assert.NotNull(gang);
Assert.Equal(dummy, gang);
}
}

View File

@@ -32,7 +32,7 @@
<ProjectReference Include="..\GangsImpl\Mock\Mock.csproj" />
<ProjectReference Include="..\GangsImpl\SQLite\SQLite.csproj" />
<ProjectReference Include="..\GangsImpl\SQL\SQL.csproj" />
<ProjectReference Include="..\Core\Core.csproj" />
<ProjectReference Include="..\Gangs\Gangs.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,34 @@
using GangsAPI.Permissions;
using Mock;
namespace GangsTest.Permissions;
public class GangRankTests {
[Fact]
public void Rank_Owner_HasAllPerms() {
var rank = new MockGangRank(0, "Owner", IGangRank.Permissions.OWNER);
foreach (var perm in Enum.GetValues<IGangRank.Permissions>()) {
Assert.True(rank.Perms.HasFlag(perm));
}
}
[Fact]
public void Rank_Admin_HasAllPerms() {
var rank =
new MockGangRank(1, "Admin", IGangRank.Permissions.ADMINISTRATOR);
foreach (var perm in Enum.GetValues<IGangRank.Permissions>()) {
if (perm == IGangRank.Permissions.OWNER)
Assert.False(rank.Perms.HasFlag(perm));
else
Assert.True(rank.Perms.HasFlag(perm));
}
}
[Fact]
public void Rank_Admin_Fields() {
var rank = new MockGangRank(0, "Owner");
Assert.Equal("Owner", rank.Name);
Assert.Equal(0, rank.Rank);
Assert.Equal(0, (int)rank.Perms);
}
}