This commit is contained in:
2026-04-15 15:19:28 +08:00
commit 03229f23d4
159 changed files with 12538 additions and 0 deletions

51
tests/config_sync_test.py Normal file
View File

@@ -0,0 +1,51 @@
from __future__ import annotations
import difflib
import sys
from pathlib import Path
def normalize(text: str) -> str:
return text.replace("\r\n", "\n").strip() + "\n"
def assert_same_file(expected: Path, actual: Path) -> None:
expected_text = normalize(expected.read_text(encoding="utf-8"))
actual_text = normalize(actual.read_text(encoding="utf-8"))
if expected_text == actual_text:
return
diff = "".join(
difflib.unified_diff(
expected_text.splitlines(keepends=True),
actual_text.splitlines(keepends=True),
fromfile=str(expected),
tofile=str(actual),
)
)
raise AssertionError(diff)
def main() -> int:
project_root = Path(__file__).resolve().parents[1]
source_config = project_root / "config" / "server.conf"
build_configs = [
project_root / "build" / "windows" / "x64" / "debug" / "config" / "server.conf",
project_root / "build" / "windows" / "x64" / "release" / "config" / "server.conf",
]
missing = [path for path in build_configs if not path.is_file()]
if missing:
raise AssertionError(
"missing build config(s): " + ", ".join(str(path) for path in missing)
)
for build_config in build_configs:
assert_same_file(source_config, build_config)
print("config sync test passed")
return 0
if __name__ == "__main__":
sys.exit(main())

162
tests/crypto_smoke_test.cpp Normal file
View File

@@ -0,0 +1,162 @@
#include "dps/app.hpp"
#include "dps/config.hpp"
#include <cassert>
#include <cstdio>
#include <cstdlib>
#include <filesystem>
#include <fstream>
#include <string>
#include <vector>
#include <openssl/evp.h>
#include <zlib.h>
namespace fs = std::filesystem;
namespace {
void set_env(const char *name, const char *value) {
#ifdef _WIN32
_putenv_s(name, value);
#else
setenv(name, value, 1);
#endif
}
void clear_env(const char *name) {
#ifdef _WIN32
_putenv_s(name, "");
#else
unsetenv(name);
#endif
}
std::vector<unsigned char> decode_base64(const std::string &text) {
std::string normalized = text;
while (normalized.size() % 4 != 0) normalized.push_back('=');
std::vector<unsigned char> decoded((normalized.size() / 4) * 3);
const int size = EVP_DecodeBlock(decoded.data(),
reinterpret_cast<const unsigned char *>(normalized.data()),
static_cast<int>(normalized.size()));
assert(size >= 0);
std::size_t padding = 0;
if (!normalized.empty() && normalized.back() == '=') ++padding;
if (normalized.size() > 1 && normalized[normalized.size() - 2] == '=') ++padding;
decoded.resize(static_cast<std::size_t>(size) - padding);
return decoded;
}
std::string gunzip_bytes(const std::vector<unsigned char> &compressed) {
z_stream stream{};
stream.next_in = const_cast<Bytef *>(reinterpret_cast<const Bytef *>(compressed.data()));
stream.avail_in = static_cast<uInt>(compressed.size());
const int init = inflateInit2(&stream, 15 + 16);
assert(init == Z_OK);
std::string output;
std::vector<char> buffer(4096);
int ret = Z_OK;
while (ret == Z_OK) {
stream.next_out = reinterpret_cast<Bytef *>(buffer.data());
stream.avail_out = static_cast<uInt>(buffer.size());
ret = inflate(&stream, Z_NO_FLUSH);
output.append(buffer.data(), buffer.size() - stream.avail_out);
}
inflateEnd(&stream);
assert(ret == Z_STREAM_END);
return output;
}
void test_crypto() {
dps::LegacyCrypto crypto("unit-test-secret");
const std::string password = "P@ssw0rd!";
const std::string hashed = crypto.hash_password(password);
assert(!hashed.empty());
assert(hashed.rfind("$2", 0) == 0);
assert(crypto.verify_password(password, hashed));
assert(!crypto.verify_password(password, password));
const std::string token = crypto.issue_jwt("tester", 60);
assert(!token.empty());
const auto subject = crypto.validate_jwt(token);
assert(subject.has_value());
assert(subject.value() == "tester");
assert(!crypto.validate_jwt(token + "broken").has_value());
const std::string gzip_payload = crypto.gzip_base64("{\"hello\":\"world\"}");
assert(!gzip_payload.empty());
const std::string inflated = gunzip_bytes(decode_base64(gzip_payload));
assert(inflated == "{\"hello\":\"world\"}");
const std::string rsa_tool = crypto.rsa_private_encrypt_tool("abc123");
const std::string rsa_script = crypto.rsa_private_encrypt_script("xyz789");
assert(!rsa_tool.empty());
assert(!rsa_script.empty());
assert(rsa_tool != "abc123");
assert(rsa_script != "xyz789");
}
void test_config() {
const fs::path path = fs::temp_directory_path() / "dps-config-smoke.conf";
{
std::ofstream out(path);
out << "server.host=0.0.0.0\n";
out << "server.port=8651\n";
out << "server.trust_proxy=true\n";
out << "server.proxy_header=x-real-ip\n";
out << "database.host=127.0.0.1\n";
out << "database.name=rindro\n";
out << "database.user=root\n";
out << "database.password=test\n";
out << "database.ssl_mode=verify_ca\n";
out << "database.ssl_ca=C:/certs/demo-ca.pem\n";
out << "database.plugin_dir=C:/mariadb/plugin\n";
}
set_env("DPS_SERVER_HOST", "10.0.0.8");
set_env("DPS_SERVER_PROXY_HEADER", "x-forwarded-for");
set_env("DPS_DB_HOST", "192.168.0.10");
set_env("DPS_DB_USER", "tester");
set_env("DPS_DB_PASSWORD", "secret");
set_env("DPS_DB_NAME", "demo");
set_env("DPS_DB_SSL_MODE", "disable");
set_env("DPS_DB_SSL_CA", "D:/certs/dev-ca.pem");
set_env("DPS_DB_PLUGIN_DIR", "D:/mariadb/plugin");
const dps::AppConfig config = dps::load_config(path.string());
assert(config.server.host == "10.0.0.8");
assert(config.server.port == 8651);
assert(config.server.trust_proxy);
assert(config.server.proxy_header == "x-forwarded-for");
assert(config.database.host == "192.168.0.10");
assert(config.database.user == "tester");
assert(config.database.password == "secret");
assert(config.database.name == "demo");
assert(config.database.ssl_mode == "disable");
assert(config.database.ssl_ca == "D:/certs/dev-ca.pem");
assert(config.database.plugin_dir == "D:/mariadb/plugin");
clear_env("DPS_SERVER_HOST");
clear_env("DPS_SERVER_PROXY_HEADER");
clear_env("DPS_DB_HOST");
clear_env("DPS_DB_USER");
clear_env("DPS_DB_PASSWORD");
clear_env("DPS_DB_NAME");
clear_env("DPS_DB_SSL_MODE");
clear_env("DPS_DB_SSL_CA");
clear_env("DPS_DB_PLUGIN_DIR");
std::remove(path.string().c_str());
}
} // namespace
int main() {
test_crypto();
test_config();
return 0;
}

View File

@@ -0,0 +1,44 @@
#include "dps/fs_utils.hpp"
#include <cstdlib>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <vector>
namespace fs = std::filesystem;
namespace {
std::string utf8_text() {
return "2_\xE5\x87\x8C\xE4\xBC\x97";
}
int run_test() {
const fs::path root = fs::temp_directory_path() / "dps-fs-utils-smoke";
const fs::path nested = root / dps::path_from_utf8(utf8_text());
const fs::path file = nested / "img.png";
fs::remove_all(root);
fs::create_directories(nested);
{
std::ofstream output(file, std::ios::binary);
output << "png-data";
}
const std::vector<unsigned char> bytes = dps::read_binary_file(file);
if (bytes.size() != 8) {
std::cerr << "unexpected file size: " << bytes.size() << "\n";
return EXIT_FAILURE;
}
fs::remove_all(root);
return EXIT_SUCCESS;
}
} // namespace
int main() {
return run_test();
}

View File

@@ -0,0 +1,31 @@
from __future__ import annotations
import re
import sys
from pathlib import Path
def main() -> int:
source = Path(__file__).resolve().parents[1] / "src" / "app.cpp"
text = source.read_text(encoding="utf-8-sig")
match = re.search(
r"Response Application::handle_get_info\(const Request &request\) \{(?P<body>.*?)\n\}",
text,
re.S,
)
if not match:
raise AssertionError("handle_get_info definition not found")
body = match.group("body")
if 'body["level"] = user->level;' not in body:
raise AssertionError('missing `body["level"] = user->level;` in handle_get_info')
if 'body["uservip"] = membership_label(user->level);' not in body:
raise AssertionError('missing dynamic `uservip` mapping in handle_get_info')
print("get_info level contract test passed")
return 0
if __name__ == "__main__":
sys.exit(main())

18
tests/json_smoke_test.cpp Normal file
View File

@@ -0,0 +1,18 @@
#include "dps/json.hpp"
#include <cstdlib>
#include <iostream>
int main() {
const dps::Json value = dps::Json::parse("{\"hello\":\"world\",\"n\":1,\"list\":[true,false]}");
if (value["hello"].to_string_or("") != "world") {
std::cerr << "json parse failed\n";
return EXIT_FAILURE;
}
if (value["n"].to_int_or(0) != 1) {
std::cerr << "json number failed\n";
return EXIT_FAILURE;
}
std::cout << "json smoke test passed\n";
return EXIT_SUCCESS;
}

View File

@@ -0,0 +1,25 @@
#include "dps/labels.hpp"
#include <cstdlib>
#include <iostream>
namespace {
bool expect_label(int level, const std::string &expected) {
const std::string actual = dps::membership_label(level);
if (actual == expected) {
return true;
}
std::cerr << "unexpected label for level " << level << "\n";
return false;
}
} // namespace
int main() {
if (!expect_label(1, "\xE9\xBB\x84\xE9\x87\x91\xE4\xBC\x9A\xE5\x91\x98")) return EXIT_FAILURE;
if (!expect_label(2, "\xE9\x92\xBB\xE7\x9F\xB3\xE4\xBC\x9A\xE5\x91\x98")) return EXIT_FAILURE;
if (!expect_label(3, "\xE8\x87\xB3\xE8\x87\xBB\xE4\xBC\x9A\xE5\x91\x98")) return EXIT_FAILURE;
if (!expect_label(99, "\xE9\xBB\x84\xE9\x87\x91\xE4\xBC\x9A\xE5\x91\x98")) return EXIT_FAILURE;
return EXIT_SUCCESS;
}