TDengine/source/dnode/mnode/impl/test/xnode/xnodeRealFunctionsTest.cpp
guichuan zhang 2c62466aa0
feat(taosx): support distributed taosx (#34126)
* feat: add xnode syntax

* refactor(xnode): reduce function complexity

* chore: add lost xnode.h file

* feat(xnode): create xnode task

* chore: fix double free error

* add xnoded

* start xnoded as subprocess

* complete xnode task feature

* complete show xnode jobs feature

* complete with option feature

* complete alter xnode job feature

* complete alter xnode task feature

* complete user pass feature

* clean code

* modify status type as char

* fix leader ep null

* fix start task req null

* fix pass id for status

* support timeout msg

* drop xnode task relative jobs

* clean code

* wip

* chore: add test cases for xnode

* chore: fix 3.0 merge changes

* fix drain core dump and create task core dump

* add password check

* retrieve xnode status from xnoded

* pass integer as double to cjson

* add some debug log

* add some job log

* fix start task lock

* do not handle http response

* fix coredump drop xnode task by name

* support start/stop/drop task by name

* remove mock xnoded

* support unix socket

* kill pre-xnoded before start

* support dnode close xnoded

* test(xnode): add unit test cases for xnode

* rebalance support where clause

* fix some test issue

* unformat http post content json string

* add xnode zh doc

* modify drain description

* remove job create/stop/drop operation

* support rebalance all without where condition

* support alter task by name

* add NULL param for mndCheckOperPrivilege

* add xnode txnode module for libmnode.a

* code clean

* change parser len to 4096

* clean code

* chore: try to fix gtest/gtest.h not found

* chore: fix markdown files

* chore: fix markdown in zh

* chore: fix enum issue and add ci

* chore: fix test case problem

* chore: fix pKeyVal overflow

* chore: rename to 排空节点

* chore: external cmake remove parallel

* chore: add DEP_ext_gtest for xnode test

* chore: fix gtest errors

* chore: remove gtest pthread lib

* chore: fix data type not match

* chore: fix some lint errors

* chore: fix void unlink

* chore: fix return with null pointer check

* chore: fix pointer double free and xnodeMemoryTest strncpy null

* chore: fix xnode encode action invalid datelen

* chore: remove TD_LINUX condition

* chore: use PRIu64 denote long long

* chore: fix task parser NULL and allow no with clause

* fix(xnode): fix windows build error

* chore: fix windows curl error

* chore: fix test case ins_tables relative error

* chore: fix memory leak

* docs: update taosx docs

* chore: update taosx docs

* chore: add role priviledge table

* chore: fix error code doc

* chore: fix test_xnode.py

* chore: fix doc typo

* fix: ci error while run test_user_privilege_sysinfo.py

---------

Co-authored-by: Linhe Huo <linhehuo@gmail.com>
Co-authored-by: huohong <sallyhuo@taosdata.com>
Co-authored-by: Simon Guan <guanshengliang@qq.com>
2026-01-01 14:51:03 +08:00

363 lines
10 KiB
C++

/**
* @file xnodeRealFunctionsTest.cpp
* @brief Test real XNode utility functions: checkPasswordFmt and swapFields
* @version 2.0
* @date 2025-12-25
*
* This version is completely independent from TDengine internal headers
* to avoid macro conflicts with C++ standard library.
*/
#include <gtest/gtest.h>
#include <cstdint>
#include <cstdlib>
#include <cstring>
// Define constants that would come from TDengine headers
#define TSDB_PASSWORD_MIN_LEN 8
#define TSDB_PASSWORD_MAX_LEN 32
#define TSDB_CODE_MND_INVALID_PASS_FORMAT -1001
#define TSDB_CODE_PAR_PASSWD_TOO_SHORT_OR_EMPTY -1002
#define TSDB_CODE_PAR_NAME_OR_PASSWD_TOO_LONG -1003
// Mock global config (normally from tglobal.h)
static int32_t tsEnableStrongPassword = 0;
// Mock function (normally from common library)
static bool taosIsComplexString(const char* pwd) {
// Simple implementation for testing
bool hasUpper = false, hasLower = false, hasDigit = false, hasSpecial = false;
for (const char* p = pwd; *p; p++) {
if (*p >= 'A' && *p <= 'Z')
hasUpper = true;
else if (*p >= 'a' && *p <= 'z')
hasLower = true;
else if (*p >= '0' && *p <= '9')
hasDigit = true;
else
hasSpecial = true;
}
// A complex string has at least 3 of the 4 types
int count = (hasUpper ? 1 : 0) + (hasLower ? 1 : 0) + (hasDigit ? 1 : 0) + (hasSpecial ? 1 : 0);
return count >= 3;
}
// Copied utility functions from mndXnode.c for testing
extern "C" {
int32_t checkPasswordFmt_test(const char* pwd) {
if (strcmp(pwd, "taosdata") == 0) {
return 0;
}
if (tsEnableStrongPassword == 0) {
for (char c = *pwd; c != 0; c = *(++pwd)) {
if (c == ' ' || c == '\'' || c == '\"' || c == '`' || c == '\\') {
return TSDB_CODE_MND_INVALID_PASS_FORMAT;
}
}
return 0;
}
int32_t len = strlen(pwd);
if (len < TSDB_PASSWORD_MIN_LEN) {
return TSDB_CODE_PAR_PASSWD_TOO_SHORT_OR_EMPTY;
}
if (len > TSDB_PASSWORD_MAX_LEN) {
return TSDB_CODE_PAR_NAME_OR_PASSWD_TOO_LONG;
}
if (taosIsComplexString(pwd)) {
return 0;
}
return TSDB_CODE_MND_INVALID_PASS_FORMAT;
}
void swapFields_test(int32_t* newLen, char** ppNewStr, int32_t* oldLen, char** ppOldStr) {
if (*newLen > 0) {
int32_t tempLen = *newLen;
*newLen = *oldLen;
*oldLen = tempLen;
char* tempStr = *ppNewStr;
*ppNewStr = *ppOldStr;
*ppOldStr = tempStr;
}
}
} // extern "C"
class XnodeRealFunctionsTest : public ::testing::Test {
protected:
void SetUp() override {
// Reset to default (weak password mode)
tsEnableStrongPassword = 0;
}
void TearDown() override {}
};
// Test checkPasswordFmt function
TEST_F(XnodeRealFunctionsTest, CheckPasswordFormat_TaosData) {
// "taosdata" is always allowed
EXPECT_EQ(checkPasswordFmt_test("taosdata"), 0);
}
TEST_F(XnodeRealFunctionsTest, CheckPasswordFormat_InvalidCharacters) {
// Test invalid characters (space, quotes, backtick, backslash)
EXPECT_NE(checkPasswordFmt_test("pass word"), 0); // space
EXPECT_NE(checkPasswordFmt_test("pass'word"), 0); // single quote
EXPECT_NE(checkPasswordFmt_test("pass\"word"), 0); // double quote
EXPECT_NE(checkPasswordFmt_test("pass`word"), 0); // backtick
EXPECT_NE(checkPasswordFmt_test("pass\\word"), 0); // backslash
}
TEST_F(XnodeRealFunctionsTest, CheckPasswordFormat_ValidSimple) {
// Valid simple passwords (when strong password is disabled)
EXPECT_EQ(checkPasswordFmt_test("password"), 0);
EXPECT_EQ(checkPasswordFmt_test("Pass123"), 0);
EXPECT_EQ(checkPasswordFmt_test("test_pass"), 0);
EXPECT_EQ(checkPasswordFmt_test("pass-word"), 0);
}
TEST_F(XnodeRealFunctionsTest, CheckPasswordFormat_MinLength_WeakMode) {
// In weak password mode, short passwords are allowed
tsEnableStrongPassword = 0;
EXPECT_EQ(checkPasswordFmt_test("abc"), 0);
EXPECT_EQ(checkPasswordFmt_test("a"), 0);
}
TEST_F(XnodeRealFunctionsTest, CheckPasswordFormat_MinLength_StrongMode) {
// In strong password mode, short passwords are rejected
tsEnableStrongPassword = 1;
EXPECT_EQ(checkPasswordFmt_test("abc"), TSDB_CODE_PAR_PASSWD_TOO_SHORT_OR_EMPTY);
EXPECT_EQ(checkPasswordFmt_test("1234567"), TSDB_CODE_PAR_PASSWD_TOO_SHORT_OR_EMPTY);
}
TEST_F(XnodeRealFunctionsTest, CheckPasswordFormat_MaxLength_StrongMode) {
// Test maximum length (32 chars for TSDB_PASSWORD_MAX_LEN)
tsEnableStrongPassword = 1;
char longPass[100];
memset(longPass, 'a', sizeof(longPass) - 1);
longPass[sizeof(longPass) - 1] = '\0';
EXPECT_EQ(checkPasswordFmt_test(longPass), TSDB_CODE_PAR_NAME_OR_PASSWD_TOO_LONG);
}
TEST_F(XnodeRealFunctionsTest, CheckPasswordFormat_Complex_StrongMode) {
// Complex passwords with numbers, uppercase, lowercase, special chars
tsEnableStrongPassword = 1;
// These should pass (complex enough)
EXPECT_EQ(checkPasswordFmt_test("Abc123!@#"), 0);
EXPECT_EQ(checkPasswordFmt_test("Test_Pass_123"), 0);
EXPECT_EQ(checkPasswordFmt_test("MyP@ssw0rd"), 0);
EXPECT_EQ(checkPasswordFmt_test("ComplexPass1!"), 0);
}
TEST_F(XnodeRealFunctionsTest, CheckPasswordFormat_NotComplex_StrongMode) {
// Passwords that aren't complex enough should fail in strong mode
tsEnableStrongPassword = 1;
// Only lowercase
EXPECT_EQ(checkPasswordFmt_test("abcdefgh"), TSDB_CODE_MND_INVALID_PASS_FORMAT);
// Only uppercase
EXPECT_EQ(checkPasswordFmt_test("ABCDEFGH"), TSDB_CODE_MND_INVALID_PASS_FORMAT);
// Only digits
EXPECT_EQ(checkPasswordFmt_test("12345678"), TSDB_CODE_MND_INVALID_PASS_FORMAT);
}
// Test swapFields function
TEST_F(XnodeRealFunctionsTest, SwapFields_BothNonEmpty) {
int32_t oldLen = 10;
int32_t newLen = 20;
char* oldStr = (char*)malloc(oldLen);
char* newStr = (char*)malloc(newLen);
strcpy(oldStr, "old");
strcpy(newStr, "new_string");
char* origOldStr = oldStr;
char* origNewStr = newStr;
swapFields_test(&newLen, &newStr, &oldLen, &oldStr);
// After swap:
// - oldLen should be 20 (original newLen)
// - newLen should be 10 (original oldLen)
// - oldStr should point to what newStr pointed to
// - newStr should point to what oldStr pointed to
EXPECT_EQ(oldLen, 20);
EXPECT_EQ(newLen, 10);
EXPECT_EQ(oldStr, origNewStr);
EXPECT_EQ(newStr, origOldStr);
free(oldStr);
free(newStr);
}
TEST_F(XnodeRealFunctionsTest, SwapFields_NewLenZero) {
int32_t oldLen = 10;
int32_t newLen = 0;
char* oldStr = (char*)malloc(oldLen);
char* newStr = nullptr;
strcpy(oldStr, "old");
int32_t origOldLen = oldLen;
char* origOldStr = oldStr;
swapFields_test(&newLen, &newStr, &oldLen, &oldStr);
// When newLen is 0, no swap should occur
EXPECT_EQ(oldLen, origOldLen);
EXPECT_EQ(oldStr, origOldStr);
EXPECT_EQ(newLen, 0);
EXPECT_EQ(newStr, nullptr);
free(oldStr);
}
TEST_F(XnodeRealFunctionsTest, SwapFields_MultipleSwaps) {
int32_t len1 = 5;
int32_t len2 = 10;
int32_t len3 = 15;
char* str1 = (char*)malloc(len1);
char* str2 = (char*)malloc(len2);
char* str3 = (char*)malloc(len3);
strcpy(str1, "aaa");
strcpy(str2, "bbbbb");
strcpy(str3, "ccccccccc");
char* orig1 = str1;
char* orig2 = str2;
char* orig3 = str3;
// Swap str1 and str2
swapFields_test(&len2, &str2, &len1, &str1);
EXPECT_EQ(len1, 10);
EXPECT_EQ(len2, 5);
EXPECT_EQ(str1, orig2);
EXPECT_EQ(str2, orig1);
// Swap str2 and str3
swapFields_test(&len3, &str3, &len2, &str2);
EXPECT_EQ(len2, 15);
EXPECT_EQ(len3, 5);
EXPECT_EQ(str2, orig3);
EXPECT_EQ(str3, orig1);
free(str1);
free(str2);
free(str3);
}
TEST_F(XnodeRealFunctionsTest, SwapFields_SamePointers) {
int32_t len = 10;
char* str = (char*)malloc(len);
strcpy(str, "test");
// Swapping with itself
swapFields_test(&len, &str, &len, &str);
// Values should remain the same
EXPECT_EQ(len, 10);
EXPECT_STREQ(str, "test");
free(str);
}
TEST_F(XnodeRealFunctionsTest, SwapFields_EdgeCase_EmptyStrings) {
int32_t oldLen = 1;
int32_t newLen = 1;
char* oldStr = (char*)malloc(oldLen);
char* newStr = (char*)malloc(newLen);
oldStr[0] = '\0';
newStr[0] = '\0';
char* origOldStr = oldStr;
char* origNewStr = newStr;
swapFields_test(&newLen, &newStr, &oldLen, &oldStr);
EXPECT_EQ(oldLen, 1);
EXPECT_EQ(newLen, 1);
EXPECT_EQ(oldStr, origNewStr);
EXPECT_EQ(newStr, origOldStr);
free(oldStr);
free(newStr);
}
// Integration test - test the swapFields usage pattern from mndXnodeActionUpdate
TEST_F(XnodeRealFunctionsTest, SwapFields_UpdatePattern) {
// Simulate the pattern used in mndXnodeActionUpdate
struct MockXnodeObj {
int32_t statusLen;
char* status;
};
MockXnodeObj oldObj = {0};
MockXnodeObj newObj = {0};
// Old object has "online" status
oldObj.statusLen = 7;
oldObj.status = (char*)malloc(oldObj.statusLen);
strcpy(oldObj.status, "online");
// New object has "offline" status
newObj.statusLen = 8;
newObj.status = (char*)malloc(newObj.statusLen);
strcpy(newObj.status, "offline");
char* oldOrigStatus = oldObj.status;
char* newOrigStatus = newObj.status;
// Swap status fields (as done in update)
swapFields_test(&newObj.statusLen, &newObj.status, &oldObj.statusLen, &oldObj.status);
// After swap, oldObj should have the new status
EXPECT_EQ(oldObj.statusLen, 8);
EXPECT_EQ(oldObj.status, newOrigStatus);
EXPECT_STREQ(oldObj.status, "offline");
// newObj should have the old status (which will be freed)
EXPECT_EQ(newObj.statusLen, 7);
EXPECT_EQ(newObj.status, oldOrigStatus);
EXPECT_STREQ(newObj.status, "online");
// Cleanup
free(oldObj.status);
free(newObj.status);
}
TEST_F(XnodeRealFunctionsTest, CheckPasswordFormat_SpecialValidChars) {
// Test that commonly used special characters in passwords work
tsEnableStrongPassword = 0;
EXPECT_EQ(checkPasswordFmt_test("pass@123"), 0);
EXPECT_EQ(checkPasswordFmt_test("pass#word"), 0);
EXPECT_EQ(checkPasswordFmt_test("pass$123"), 0);
EXPECT_EQ(checkPasswordFmt_test("pass%word"), 0);
EXPECT_EQ(checkPasswordFmt_test("pass&123"), 0);
EXPECT_EQ(checkPasswordFmt_test("pass!word"), 0);
}
TEST_F(XnodeRealFunctionsTest, CheckPasswordFormat_NumbersOnly_WeakMode) {
tsEnableStrongPassword = 0;
EXPECT_EQ(checkPasswordFmt_test("12345678"), 0);
EXPECT_EQ(checkPasswordFmt_test("99999999"), 0);
}
TEST_F(XnodeRealFunctionsTest, CheckPasswordFormat_MixedCase_WeakMode) {
tsEnableStrongPassword = 0;
EXPECT_EQ(checkPasswordFmt_test("PaSsWoRd"), 0);
EXPECT_EQ(checkPasswordFmt_test("UPPERCASE"), 0);
EXPECT_EQ(checkPasswordFmt_test("lowercase"), 0);
}