mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-02 02:43:44 +00:00
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>
335 lines
10 KiB
C++
335 lines
10 KiB
C++
#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¶m=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);
|
|
}
|