feat: web dashboard and playground work

The NexusDashboard-parity dashboard (dDashboardServer) and everything built on it on the experimental branch:
accounts, characters, properties and moderation tools, permissions shared with in-game slash commands, economy
reports, World 3D and property 3D views with client scenery, scheduled events (features, vanity changes, live
events, announcements, restarts), vanity files and events, the CDClient browser, the message inspector with saved
captures, chat filter tools, community challenges, live ops, the AI moderator helper, and the server-side changes
they need.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Aaron Kimbrell
2026-09-26 17:52:10 -05:00
parent 854d02f509
commit e213a7aeff
681 changed files with 88310 additions and 1321 deletions

View File

@@ -0,0 +1,334 @@
#include <gtest/gtest.h>
#include <memory>
#include "HTTPContext.h"
#include "Web.h"
// Note: These tests use mock implementations to avoid circular dependencies.
// In a real deployment, DashboardAuthService would be used instead.
// Mock implementation of token verification for testing
namespace {
bool VerifyTokenMock(const std::string& token, std::string& outUsername, uint8_t& outGmLevel) {
// For testing: valid tokens are prefixed with "valid_"
if (token.substr(0, 6) == "valid_") {
outUsername = "testuser";
outGmLevel = 1; // GM level 1
return true;
}
if (token == "admin_token") {
outUsername = "admin";
outGmLevel = 9; // GM level 9 (admin)
return true;
}
return false;
}
}
// Test HTTPContext functionality
class HTTPContextTest : public ::testing::Test {
protected:
HTTPContext context;
};
TEST_F(HTTPContextTest, DefaultConstructorInitializesFields) {
EXPECT_FALSE(context.isAuthenticated);
EXPECT_EQ(context.authenticatedUser, "");
EXPECT_EQ(context.gmLevel, 0);
EXPECT_EQ(context.method, "");
EXPECT_EQ(context.path, "");
EXPECT_EQ(context.queryString, "");
EXPECT_EQ(context.body, "");
EXPECT_EQ(context.clientIP, "");
}
TEST_F(HTTPContextTest, SetHeaderAndGetHeaderCaseInsensitive) {
context.SetHeader("Content-Type", "application/json");
EXPECT_EQ(context.GetHeader("Content-Type"), "application/json");
EXPECT_EQ(context.GetHeader("content-type"), "application/json");
EXPECT_EQ(context.GetHeader("CONTENT-TYPE"), "application/json");
}
TEST_F(HTTPContextTest, GetHeaderReturnsEmptyStringForMissingHeader) {
EXPECT_EQ(context.GetHeader("NonExistent"), "");
}
TEST_F(HTTPContextTest, SetHeaderMultipleHeaders) {
context.SetHeader("Authorization", "Bearer token123");
context.SetHeader("Cookie", "session=xyz");
context.SetHeader("User-Agent", "TestClient/1.0");
EXPECT_EQ(context.GetHeader("authorization"), "Bearer token123");
EXPECT_EQ(context.GetHeader("cookie"), "session=xyz");
EXPECT_EQ(context.GetHeader("user-agent"), "TestClient/1.0");
}
TEST_F(HTTPContextTest, AuthenticationFields) {
context.isAuthenticated = true;
context.authenticatedUser = "testuser";
context.gmLevel = 5;
EXPECT_TRUE(context.isAuthenticated);
EXPECT_EQ(context.authenticatedUser, "testuser");
EXPECT_EQ(context.gmLevel, 5);
}
TEST_F(HTTPContextTest, UserDataMap) {
context.userData["key1"] = "value1";
context.userData["key2"] = "value2";
EXPECT_EQ(context.userData["key1"], "value1");
EXPECT_EQ(context.userData["key2"], "value2");
EXPECT_TRUE(context.userData.count("key1") > 0);
}
// Test token extraction utilities
namespace TokenExtraction {
static std::string ExtractTokenFromQueryString(const std::string& queryString) {
if (queryString.empty()) {
return "";
}
std::string tokenPrefix = "token=";
size_t tokenPos = queryString.find(tokenPrefix);
if (tokenPos == std::string::npos) {
return "";
}
size_t valueStart = tokenPos + tokenPrefix.length();
size_t valueEnd = queryString.find("&", valueStart);
if (valueEnd == std::string::npos) {
valueEnd = queryString.length();
}
return queryString.substr(valueStart, valueEnd - valueStart);
}
static std::string ExtractTokenFromAuthHeader(const std::string& authHeader) {
if (authHeader.empty()) {
return "";
}
if (authHeader.substr(0, 7) == "Bearer ") {
return authHeader.substr(7);
}
if (authHeader.substr(0, 6) == "Token ") {
return authHeader.substr(6);
}
return authHeader;
}
}
// Test token extraction utilities
class TokenExtractionTest : public ::testing::Test {
};
TEST_F(TokenExtractionTest, ExtractFromQueryString) {
std::string query = "token=mytoken123&other=value";
std::string token = TokenExtraction::ExtractTokenFromQueryString(query);
EXPECT_EQ(token, "mytoken123");
}
TEST_F(TokenExtractionTest, ExtractFromQueryStringWithNoOtherParams) {
std::string query = "token=simpletoken";
std::string token = TokenExtraction::ExtractTokenFromQueryString(query);
EXPECT_EQ(token, "simpletoken");
}
TEST_F(TokenExtractionTest, NoTokenInQueryString) {
std::string query = "other=value&param=test";
std::string token = TokenExtraction::ExtractTokenFromQueryString(query);
EXPECT_EQ(token, "");
}
TEST_F(TokenExtractionTest, ExtractFromBearerHeader) {
std::string header = "Bearer eyJhbGciOiJIUzI1NiJ9";
std::string token = TokenExtraction::ExtractTokenFromAuthHeader(header);
EXPECT_EQ(token, "eyJhbGciOiJIUzI1NiJ9");
}
TEST_F(TokenExtractionTest, ExtractFromTokenHeader) {
std::string header = "Token abc123xyz";
std::string token = TokenExtraction::ExtractTokenFromAuthHeader(header);
EXPECT_EQ(token, "abc123xyz");
}
TEST_F(TokenExtractionTest, ExtractRawTokenFromHeader) {
std::string header = "rawtoken123";
std::string token = TokenExtraction::ExtractTokenFromAuthHeader(header);
EXPECT_EQ(token, "rawtoken123");
}
// Test HTTPContext population scenarios
class HTTPContextPopulationTest : public ::testing::Test {
protected:
HTTPContext context;
};
TEST_F(HTTPContextPopulationTest, PopulateFromRequest) {
context.method = "POST";
context.path = "/api/auth/login";
context.queryString = "token=abc123";
context.body = "{\"username\":\"test\"}";
context.clientIP = "192.168.1.100";
EXPECT_EQ(context.method, "POST");
EXPECT_EQ(context.path, "/api/auth/login");
EXPECT_EQ(context.queryString, "token=abc123");
EXPECT_EQ(context.body, "{\"username\":\"test\"}");
EXPECT_EQ(context.clientIP, "192.168.1.100");
}
TEST_F(HTTPContextPopulationTest, MultipleHeadersWithMixedCase) {
context.SetHeader("Content-Type", "application/json");
context.SetHeader("Authorization", "Bearer token");
context.SetHeader("Accept", "application/json");
context.SetHeader("User-Agent", "TestClient");
// Verify all headers are accessible case-insensitively
EXPECT_EQ(context.GetHeader("content-type"), "application/json");
EXPECT_EQ(context.GetHeader("AUTHORIZATION"), "Bearer token");
EXPECT_EQ(context.GetHeader("accept"), "application/json");
EXPECT_EQ(context.GetHeader("USER-AGENT"), "TestClient");
}
// Integration tests for middleware chains
class MiddlewareAuthenticationFlow : public ::testing::Test {
protected:
HTTPContext context;
HTTPReply reply;
void SetUp() override {
reply.status = eHTTPStatusCode::OK;
reply.contentType = eContentType::APPLICATION_JSON;
context.path = "/api/test";
context.clientIP = "127.0.0.1";
context.method = "GET";
}
void SimulateTokenVerification(const std::string& token) {
std::string username;
uint8_t gmLevel;
if (VerifyTokenMock(token, username, gmLevel)) {
context.isAuthenticated = true;
context.authenticatedUser = username;
context.gmLevel = gmLevel;
}
}
};
TEST_F(MiddlewareAuthenticationFlow, SuccessfulAuthenticationWithQueryToken) {
context.queryString = "token=valid_token123";
// Extract token
std::string token = TokenExtraction::ExtractTokenFromQueryString(context.queryString);
EXPECT_EQ(token, "valid_token123");
// Verify token
SimulateTokenVerification(token);
EXPECT_TRUE(context.isAuthenticated);
EXPECT_EQ(context.authenticatedUser, "testuser");
EXPECT_EQ(context.gmLevel, 1);
}
TEST_F(MiddlewareAuthenticationFlow, SuccessfulAuthenticationWithBearerToken) {
context.SetHeader("Authorization", "Bearer admin_token");
// Extract token
std::string authHeader = context.GetHeader("Authorization");
std::string token = TokenExtraction::ExtractTokenFromAuthHeader(authHeader);
EXPECT_EQ(token, "admin_token");
// Verify token
SimulateTokenVerification(token);
EXPECT_TRUE(context.isAuthenticated);
EXPECT_EQ(context.authenticatedUser, "admin");
EXPECT_EQ(context.gmLevel, 9);
}
TEST_F(MiddlewareAuthenticationFlow, FailedAuthenticationInvalidToken) {
context.queryString = "token=invalid_token";
// Extract token
std::string token = TokenExtraction::ExtractTokenFromQueryString(context.queryString);
EXPECT_EQ(token, "invalid_token");
// Verify token
SimulateTokenVerification(token);
EXPECT_FALSE(context.isAuthenticated);
EXPECT_EQ(context.authenticatedUser, "");
EXPECT_EQ(context.gmLevel, 0);
}
TEST_F(MiddlewareAuthenticationFlow, NoTokenProvided) {
context.queryString = "";
// Extract token (none provided)
std::string token = TokenExtraction::ExtractTokenFromQueryString(context.queryString);
EXPECT_EQ(token, "");
// Should remain unauthenticated
EXPECT_FALSE(context.isAuthenticated);
EXPECT_EQ(context.authenticatedUser, "");
EXPECT_EQ(context.gmLevel, 0);
}
// Test authorization level checking
class AuthorizationLevelTest : public ::testing::Test {
protected:
uint8_t CheckMinimumGMLevel(uint8_t userLevel, uint8_t requiredLevel) {
return userLevel >= requiredLevel ? 1 : 0; // 1 = allowed, 0 = forbidden
}
};
TEST_F(AuthorizationLevelTest, UserCanAccessWithSufficientLevel) {
EXPECT_EQ(CheckMinimumGMLevel(9, 5), 1); // Admin (9) can access level 5
EXPECT_EQ(CheckMinimumGMLevel(5, 5), 1); // Level 5 can access level 5
EXPECT_EQ(CheckMinimumGMLevel(0, 0), 1); // Level 0 can access level 0
}
TEST_F(AuthorizationLevelTest, UserCannotAccessWithInsufficientLevel) {
EXPECT_EQ(CheckMinimumGMLevel(2, 5), 0); // Level 2 cannot access level 5
EXPECT_EQ(CheckMinimumGMLevel(0, 1), 0); // Level 0 cannot access level 1
EXPECT_EQ(CheckMinimumGMLevel(3, 9), 0); // Level 3 cannot access admin (9)
}
// Test error response formatting
class ErrorResponseTest : public ::testing::Test {
protected:
HTTPReply reply;
};
TEST_F(ErrorResponseTest, UnauthorizedResponse) {
reply.status = eHTTPStatusCode::UNAUTHORIZED;
reply.message = "{\"error\":\"Unauthorized - Authentication required\"}";
reply.contentType = eContentType::APPLICATION_JSON;
EXPECT_EQ(reply.status, eHTTPStatusCode::UNAUTHORIZED);
EXPECT_NE(reply.message.find("Unauthorized"), std::string::npos);
EXPECT_EQ(reply.contentType, eContentType::APPLICATION_JSON);
}
TEST_F(ErrorResponseTest, ForbiddenResponse) {
reply.status = eHTTPStatusCode::FORBIDDEN;
reply.message = "{\"error\":\"Forbidden - Insufficient permissions\"}";
reply.contentType = eContentType::APPLICATION_JSON;
EXPECT_EQ(reply.status, eHTTPStatusCode::FORBIDDEN);
EXPECT_NE(reply.message.find("Forbidden"), std::string::npos);
EXPECT_EQ(reply.contentType, eContentType::APPLICATION_JSON);
}
TEST_F(ErrorResponseTest, OkResponse) {
reply.status = eHTTPStatusCode::OK;
reply.message = "{\"status\":\"success\",\"data\":{}}";
reply.contentType = eContentType::APPLICATION_JSON;
EXPECT_EQ(reply.status, eHTTPStatusCode::OK);
EXPECT_EQ(reply.contentType, eContentType::APPLICATION_JSON);
}