feat: Add initial implementation of CI/CD workflows, documentation, and testing framework

This commit is contained in:
HueByte
2026-02-19 05:35:05 +01:00
parent 3188d18da2
commit 56ff6197a2
21 changed files with 888 additions and 2 deletions
+5
View File
@@ -0,0 +1,5 @@
<Project>
<PropertyGroup>
<Version>0.1.0</Version>
</PropertyGroup>
</Project>
+239 -1
View File
@@ -181,6 +181,237 @@ public static class ThemeManager
}
};
private static readonly Theme DraculaTheme = new()
{
Name = "Dracula",
Base = new ThemeColors
{
Foreground = "White",
Background = "Black",
FocusForeground = "Black",
FocusBackground = "Magenta"
},
Menu = new ThemeColors
{
Foreground = "BrightMagenta",
Background = "Black",
FocusForeground = "Black",
FocusBackground = "BrightMagenta"
},
Dialog = new ThemeColors
{
Foreground = "White",
Background = "DarkGray",
FocusForeground = "Black",
FocusBackground = "Magenta"
},
Status = new ThemeColors
{
Foreground = "BrightMagenta",
Background = "Black",
FocusForeground = "BrightMagenta",
FocusBackground = "Black"
}
};
private static readonly Theme MonokaiTheme = new()
{
Name = "Monokai",
Base = new ThemeColors
{
Foreground = "White",
Background = "Black",
FocusForeground = "Black",
FocusBackground = "BrightYellow"
},
Menu = new ThemeColors
{
Foreground = "BrightYellow",
Background = "Black",
FocusForeground = "Black",
FocusBackground = "BrightYellow"
},
Dialog = new ThemeColors
{
Foreground = "White",
Background = "DarkGray",
FocusForeground = "Black",
FocusBackground = "BrightYellow"
},
Status = new ThemeColors
{
Foreground = "BrightYellow",
Background = "Black",
FocusForeground = "BrightYellow",
FocusBackground = "Black"
}
};
private static readonly Theme NordTheme = new()
{
Name = "Nord",
Base = new ThemeColors
{
Foreground = "White",
Background = "DarkBlue",
FocusForeground = "BrightCyan",
FocusBackground = "Blue"
},
Menu = new ThemeColors
{
Foreground = "White",
Background = "Blue",
FocusForeground = "BrightCyan",
FocusBackground = "DarkBlue"
},
Dialog = new ThemeColors
{
Foreground = "White",
Background = "Blue",
FocusForeground = "BrightCyan",
FocusBackground = "DarkBlue"
},
Status = new ThemeColors
{
Foreground = "BrightCyan",
Background = "DarkBlue",
FocusForeground = "BrightCyan",
FocusBackground = "DarkBlue"
}
};
private static readonly Theme GruvboxTheme = new()
{
Name = "Gruvbox",
Base = new ThemeColors
{
Foreground = "BrightYellow",
Background = "Black",
FocusForeground = "Black",
FocusBackground = "DarkYellow"
},
Menu = new ThemeColors
{
Foreground = "BrightYellow",
Background = "Black",
FocusForeground = "Black",
FocusBackground = "DarkYellow"
},
Dialog = new ThemeColors
{
Foreground = "BrightYellow",
Background = "DarkGray",
FocusForeground = "Black",
FocusBackground = "DarkYellow"
},
Status = new ThemeColors
{
Foreground = "DarkYellow",
Background = "Black",
FocusForeground = "DarkYellow",
FocusBackground = "Black"
}
};
private static readonly Theme OceanTheme = new()
{
Name = "Ocean",
Base = new ThemeColors
{
Foreground = "BrightCyan",
Background = "DarkBlue",
FocusForeground = "White",
FocusBackground = "DarkCyan"
},
Menu = new ThemeColors
{
Foreground = "White",
Background = "DarkCyan",
FocusForeground = "BrightCyan",
FocusBackground = "DarkBlue"
},
Dialog = new ThemeColors
{
Foreground = "White",
Background = "DarkCyan",
FocusForeground = "BrightCyan",
FocusBackground = "DarkBlue"
},
Status = new ThemeColors
{
Foreground = "BrightCyan",
Background = "DarkCyan",
FocusForeground = "BrightCyan",
FocusBackground = "DarkCyan"
}
};
private static readonly Theme HighContrastTheme = new()
{
Name = "HighContrast",
Base = new ThemeColors
{
Foreground = "BrightYellow",
Background = "Black",
FocusForeground = "Black",
FocusBackground = "BrightYellow"
},
Menu = new ThemeColors
{
Foreground = "BrightYellow",
Background = "Black",
FocusForeground = "Black",
FocusBackground = "BrightYellow"
},
Dialog = new ThemeColors
{
Foreground = "BrightYellow",
Background = "Black",
FocusForeground = "Black",
FocusBackground = "BrightYellow"
},
Status = new ThemeColors
{
Foreground = "BrightYellow",
Background = "Black",
FocusForeground = "BrightYellow",
FocusBackground = "Black"
}
};
private static readonly Theme RosePineTheme = new()
{
Name = "RosePine",
Base = new ThemeColors
{
Foreground = "White",
Background = "Black",
FocusForeground = "Black",
FocusBackground = "DarkMagenta"
},
Menu = new ThemeColors
{
Foreground = "Magenta",
Background = "Black",
FocusForeground = "White",
FocusBackground = "DarkMagenta"
},
Dialog = new ThemeColors
{
Foreground = "White",
Background = "DarkGray",
FocusForeground = "White",
FocusBackground = "DarkMagenta"
},
Status = new ThemeColors
{
Foreground = "Magenta",
Background = "Black",
FocusForeground = "Magenta",
FocusBackground = "Black"
}
};
private static readonly Theme TransparentTheme = new()
{
Name = "Transparent",
@@ -221,7 +452,14 @@ public static class ThemeManager
ClassicTheme,
LightTheme,
HackerTheme,
SolarizedTheme
SolarizedTheme,
DraculaTheme,
MonokaiTheme,
NordTheme,
GruvboxTheme,
OceanTheme,
HighContrastTheme,
RosePineTheme
];
public static List<Theme> GetAvailableThemes()
+25
View File
@@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.*" />
<PackageReference Include="xunit" Version="2.*" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.*">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\EchoHub.Core\EchoHub.Core.csproj" />
<ProjectReference Include="..\EchoHub.Server\EchoHub.Server.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,56 @@
using EchoHub.Server.Services;
using Xunit;
namespace EchoHub.Tests;
public class FileValidationHelperTests
{
[Fact]
public void IsValidImage_JpegMagicBytes_ReturnsTrue()
{
byte[] jpeg = [0xFF, 0xD8, 0xFF, 0xE0, 0x00, 0x10];
using var stream = new MemoryStream(jpeg);
Assert.True(FileValidationHelper.IsValidImage(stream));
}
[Fact]
public void IsValidImage_PngMagicBytes_ReturnsTrue()
{
byte[] png = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A];
using var stream = new MemoryStream(png);
Assert.True(FileValidationHelper.IsValidImage(stream));
}
[Fact]
public void IsValidImage_GifMagicBytes_ReturnsTrue()
{
byte[] gif = [0x47, 0x49, 0x46, 0x38, 0x39, 0x61];
using var stream = new MemoryStream(gif);
Assert.True(FileValidationHelper.IsValidImage(stream));
}
[Fact]
public void IsValidImage_WebpMagicBytes_ReturnsTrue()
{
byte[] webp = [0x52, 0x49, 0x46, 0x46, 0x00, 0x00, 0x00, 0x00, 0x57, 0x45, 0x42, 0x50];
using var stream = new MemoryStream(webp);
Assert.True(FileValidationHelper.IsValidImage(stream));
}
[Fact]
public void IsValidImage_RandomBytes_ReturnsFalse()
{
byte[] random = [0x00, 0x01, 0x02, 0x03, 0x04, 0x05];
using var stream = new MemoryStream(random);
Assert.False(FileValidationHelper.IsValidImage(stream));
}
[Fact]
public void IsValidImage_ResetsStreamPosition()
{
byte[] jpeg = [0xFF, 0xD8, 0xFF, 0xE0, 0x00, 0x10];
using var stream = new MemoryStream(jpeg);
FileValidationHelper.IsValidImage(stream);
Assert.Equal(0, stream.Position);
}
}
+68
View File
@@ -0,0 +1,68 @@
using EchoHub.Server.Services;
using Xunit;
namespace EchoHub.Tests;
public class PresenceTrackerTests
{
[Fact]
public void UserConnected_IsOnline_ReturnsTrue()
{
var tracker = new PresenceTracker();
tracker.UserConnected("conn1", Guid.NewGuid(), "alice");
Assert.True(tracker.IsOnline("alice"));
}
[Fact]
public void UserDisconnected_LastConnection_IsOnlineReturnsFalse()
{
var tracker = new PresenceTracker();
tracker.UserConnected("conn1", Guid.NewGuid(), "alice");
tracker.UserDisconnected("conn1");
Assert.False(tracker.IsOnline("alice"));
}
[Fact]
public void MultipleConnections_DisconnectOne_StillOnline()
{
var tracker = new PresenceTracker();
var userId = Guid.NewGuid();
tracker.UserConnected("conn1", userId, "alice");
tracker.UserConnected("conn2", userId, "alice");
tracker.UserDisconnected("conn1");
Assert.True(tracker.IsOnline("alice"));
}
[Fact]
public void JoinChannel_GetOnlineUsersInChannel_ReturnsUser()
{
var tracker = new PresenceTracker();
tracker.UserConnected("conn1", Guid.NewGuid(), "alice");
tracker.JoinChannel("alice", "general");
var users = tracker.GetOnlineUsersInChannel("general");
Assert.Contains("alice", users);
}
[Fact]
public void LeaveChannel_UserNoLongerInChannel()
{
var tracker = new PresenceTracker();
tracker.UserConnected("conn1", Guid.NewGuid(), "alice");
tracker.JoinChannel("alice", "general");
tracker.LeaveChannel("alice", "general");
var users = tracker.GetOnlineUsersInChannel("general");
Assert.DoesNotContain("alice", users);
}
[Fact]
public void GetChannelsForUser_ReturnsJoinedChannels()
{
var tracker = new PresenceTracker();
tracker.UserConnected("conn1", Guid.NewGuid(), "alice");
tracker.JoinChannel("alice", "general");
tracker.JoinChannel("alice", "random");
var channels = tracker.GetChannelsForUser("alice");
Assert.Contains("general", channels);
Assert.Contains("random", channels);
}
}
@@ -0,0 +1,46 @@
using EchoHub.Core.Constants;
using Xunit;
namespace EchoHub.Tests;
public class ValidationConstantsTests
{
[Theory]
[InlineData("alice", true)]
[InlineData("Bob_123", true)]
[InlineData("user-name", true)]
[InlineData("abc", true)]
[InlineData("ab", false)]
[InlineData("", false)]
[InlineData("has space", false)]
[InlineData("has@symbol", false)]
public void UsernameRegex_ValidatesCorrectly(string input, bool expected)
{
var result = ValidationConstants.UsernameRegex().IsMatch(input);
Assert.Equal(expected, result);
}
[Theory]
[InlineData("general", true)]
[InlineData("my-channel_01", true)]
[InlineData("ab", true)]
[InlineData("a", false)]
[InlineData("has space", false)]
public void ChannelNameRegex_ValidatesCorrectly(string input, bool expected)
{
var result = ValidationConstants.ChannelNameRegex().IsMatch(input);
Assert.Equal(expected, result);
}
[Theory]
[InlineData("#FF0000", true)]
[InlineData("#aabbcc", true)]
[InlineData("FF0000", false)]
[InlineData("#FFF", false)]
[InlineData("#GGGGGG", false)]
public void HexColorRegex_ValidatesCorrectly(string input, bool expected)
{
var result = ValidationConstants.HexColorRegex().IsMatch(input);
Assert.Equal(expected, result);
}
}
+1
View File
@@ -2,4 +2,5 @@
<Project Path="EchoHub.Client/EchoHub.Client.csproj" />
<Project Path="EchoHub.Core/EchoHub.Core.csproj" />
<Project Path="EchoHub.Server/EchoHub.Server.csproj" />
<Project Path="EchoHub.Tests/EchoHub.Tests.csproj" />
</Solution>