WIP: basic server, no features

This commit is contained in:
Aaron Kimbrell
2026-01-25 22:33:51 -06:00
parent c723ce2588
commit f1847d1f20
67 changed files with 7655 additions and 37 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);
}