You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
proxysql/test/tap/tests/ai_validation-t.cpp

340 lines
11 KiB

/**
* @file ai_validation-t.cpp
* @brief TAP unit tests for AI configuration validation functions
*
* Test Categories:
* 1. URL format validation (validate_url_format)
* 2. API key format validation (validate_api_key_format)
* 3. Numeric range validation (validate_numeric_range)
* 4. Provider name validation (validate_provider_name)
*
* Note: These are standalone implementations of the validation functions
* for testing purposes, matching the logic in AI_Features_Manager.cpp
*
* @date 2025-01-16
*/
#include "tap.h"
#include <string.h>
#include <cstdio>
#include <cstdlib>
// ============================================================================
// Standalone validation functions (matching AI_Features_Manager.cpp logic)
// ============================================================================
static bool validate_url_format(const char* url) {
if (!url || strlen(url) == 0) {
return true; // Empty URL is valid (will use defaults)
}
// Check for protocol prefix (http://, https://)
const char* http_prefix = "http://";
const char* https_prefix = "https://";
bool has_protocol = (strncmp(url, http_prefix, strlen(http_prefix)) == 0 ||
strncmp(url, https_prefix, strlen(https_prefix)) == 0);
if (!has_protocol) {
return false;
}
// Check for host part (at least something after ://)
const char* host_start = strstr(url, "://");
if (!host_start || strlen(host_start + 3) == 0) {
return false;
}
return true;
}
static bool validate_api_key_format(const char* key, const char* provider_name) {
(void)provider_name; // Suppress unused warning in test
if (!key || strlen(key) == 0) {
return true; // Empty key is valid for local endpoints
}
size_t len = strlen(key);
// Check for whitespace
for (size_t i = 0; i < len; i++) {
if (key[i] == ' ' || key[i] == '\t' || key[i] == '\n' || key[i] == '\r') {
return false;
}
}
// Check minimum length (most API keys are at least 20 chars)
if (len < 10) {
return false;
}
// Check for incomplete OpenAI key format
if (strncmp(key, "sk-", 3) == 0 && len < 20) {
return false;
}
// Check for incomplete Anthropic key format
if (strncmp(key, "sk-ant-", 7) == 0 && len < 25) {
return false;
}
return true;
}
static bool validate_numeric_range(const char* value, int min_val, int max_val, const char* var_name) {
(void)var_name; // Suppress unused warning in test
if (!value || strlen(value) == 0) {
return false;
}
int int_val = atoi(value);
if (int_val < min_val || int_val > max_val) {
return false;
}
return true;
}
static bool validate_provider_format(const char* provider) {
if (!provider || strlen(provider) == 0) {
return false;
}
const char* valid_formats[] = {"openai", "anthropic", NULL};
for (int i = 0; valid_formats[i]; i++) {
if (strcmp(provider, valid_formats[i]) == 0) {
return true;
}
}
return false;
}
// Test helper macros
#define TEST_URL_VALID(url) \
ok(validate_url_format(url), "URL '%s' is valid", url)
#define TEST_URL_INVALID(url) \
ok(!validate_url_format(url), "URL '%s' is invalid", url)
// ============================================================================
// Test: URL Format Validation
// ============================================================================
void test_url_validation() {
diag("=== URL Format Validation Tests ===");
// Valid URLs
TEST_URL_VALID("http://localhost:11434/v1/chat/completions");
TEST_URL_VALID("https://api.openai.com/v1/chat/completions");
TEST_URL_VALID("https://api.anthropic.com/v1/messages");
TEST_URL_VALID("http://192.168.1.1:8080/api");
TEST_URL_VALID("https://example.com");
TEST_URL_VALID(""); // Empty is valid (uses default)
TEST_URL_VALID("https://example.com/path");
TEST_URL_VALID("http://host:port/path");
TEST_URL_VALID("https://x.com"); // Minimal valid URL
// Invalid URLs
TEST_URL_INVALID("localhost:11434"); // Missing protocol
TEST_URL_INVALID("ftp://example.com"); // Wrong protocol
TEST_URL_INVALID("http://"); // Missing host
TEST_URL_INVALID("https://"); // Missing host
TEST_URL_INVALID("://example.com"); // Missing protocol
TEST_URL_INVALID("example.com"); // No protocol
}
// ============================================================================
// Test: API Key Format Validation
// ============================================================================
void test_api_key_validation() {
diag("=== API Key Format Validation Tests ===");
// Valid keys
ok(validate_api_key_format("sk-1234567890abcdef1234567890abcdef", "openai"),
"Valid OpenAI key accepted");
ok(validate_api_key_format("sk-ant-1234567890abcdef1234567890abcdef", "anthropic"),
"Valid Anthropic key accepted");
ok(validate_api_key_format("", "openai"),
"Empty key accepted (local endpoint)");
ok(validate_api_key_format("my-custom-api-key-12345", "custom"),
"Custom key format accepted");
ok(validate_api_key_format("0123456789abcdefghij", "test"),
"10-character key accepted (minimum)");
ok(validate_api_key_format("sk-proj-shortbutlongenough", "openai"),
"sk-proj- prefix key accepted if length is ok");
// Invalid keys - whitespace
ok(!validate_api_key_format("sk-1234567890 with space", "openai"),
"Key with space rejected");
ok(!validate_api_key_format("sk-1234567890\ttab", "openai"),
"Key with tab rejected");
ok(!validate_api_key_format("sk-1234567890\nnewline", "openai"),
"Key with newline rejected");
ok(!validate_api_key_format("sk-1234567890\rcarriage", "openai"),
"Key with carriage return rejected");
// Invalid keys - too short
ok(!validate_api_key_format("short", "openai"),
"Very short key rejected");
ok(!validate_api_key_format("sk-abc", "openai"),
"Incomplete OpenAI key rejected");
// Invalid keys - incomplete Anthropic format
ok(!validate_api_key_format("sk-ant-short", "anthropic"),
"Incomplete Anthropic key rejected");
}
// ============================================================================
// Test: Numeric Range Validation
// ============================================================================
void test_numeric_range_validation() {
diag("=== Numeric Range Validation Tests ===");
// Valid values
ok(validate_numeric_range("50", 0, 100, "test_var"),
"Value in middle of range accepted");
ok(validate_numeric_range("0", 0, 100, "test_var"),
"Minimum boundary value accepted");
ok(validate_numeric_range("100", 0, 100, "test_var"),
"Maximum boundary value accepted");
ok(validate_numeric_range("85", 0, 100, "ai_nl2sql_cache_similarity_threshold"),
"Cache threshold 85 in valid range");
ok(validate_numeric_range("30000", 1000, 300000, "ai_nl2sql_timeout_ms"),
"Timeout 30000ms in valid range");
ok(validate_numeric_range("1", 1, 10000, "ai_anomaly_rate_limit"),
"Rate limit 1 in valid range");
// Invalid values
ok(!validate_numeric_range("-1", 0, 100, "test_var"),
"Value below minimum rejected");
ok(!validate_numeric_range("101", 0, 100, "test_var"),
"Value above maximum rejected");
ok(!validate_numeric_range("", 0, 100, "test_var"),
"Empty value rejected");
// Note: atoi("abc") returns 0, which is in range [0,100]
// This is a known limitation of the validation function
ok(validate_numeric_range("abc", 0, 100, "test_var"),
"Non-numeric value accepted (atoi limitation: 'abc' -> 0)");
// But if the range doesn't include 0, it fails correctly
ok(!validate_numeric_range("abc", 1, 100, "test_var"),
"Non-numeric value rejected when range starts above 0");
ok(!validate_numeric_range("-5", 1, 10, "test_var"),
"Negative value rejected");
}
// ============================================================================
// Test: Provider Name Validation
// ============================================================================
void test_provider_format_validation() {
diag("=== Provider Format Validation Tests ===");
// Valid formats
ok(validate_provider_format("openai"),
"Provider format 'openai' accepted");
ok(validate_provider_format("anthropic"),
"Provider format 'anthropic' accepted");
// Invalid formats
ok(!validate_provider_format(""),
"Empty provider format rejected");
ok(!validate_provider_format("ollama"),
"Provider format 'ollama' rejected (removed)");
ok(!validate_provider_format("OpenAI"),
"Uppercase 'OpenAI' rejected (case sensitive)");
ok(!validate_provider_format("ANTHROPIC"),
"Uppercase 'ANTHROPIC' rejected (case sensitive)");
ok(!validate_provider_format("invalid"),
"Unknown provider format rejected");
ok(!validate_provider_format(" OpenAI "),
"Provider format with spaces rejected");
}
// ============================================================================
// Test: Edge Cases and Boundary Conditions
// ============================================================================
void test_edge_cases() {
diag("=== Edge Cases and Boundary Tests ===");
// NULL pointer handling - URL
ok(validate_url_format(NULL),
"NULL URL accepted (uses default)");
// NULL pointer handling - API key
ok(validate_api_key_format(NULL, "openai"),
"NULL API key accepted (uses default)");
// NULL pointer handling - Provider
ok(!validate_provider_format(NULL),
"NULL provider format rejected");
// NULL pointer handling - Numeric range
ok(!validate_numeric_range(NULL, 0, 100, "test_var"),
"NULL numeric value rejected");
// Very long URL
char long_url[512];
snprintf(long_url, sizeof(long_url),
"https://example.com/%s", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
ok(validate_url_format(long_url),
"Long URL accepted");
// URL with query string
ok(validate_url_format("https://example.com/path?query=value&other=123"),
"URL with query string accepted");
// URL with port
ok(validate_url_format("https://example.com:8080/path"),
"URL with port accepted");
// URL with fragment
ok(validate_url_format("https://example.com/path#fragment"),
"URL with fragment accepted");
// API key exactly at boundary
ok(validate_api_key_format("0123456789", "test"),
"API key with exactly 10 characters accepted");
// API key just below boundary
ok(!validate_api_key_format("012345678", "test"),
"API key with 9 characters rejected");
// OpenAI key at boundary (sk-xxxxxxxxxxxx - need at least 17 more chars)
ok(validate_api_key_format("sk-12345678901234567", "openai"),
"OpenAI key at 20 character boundary accepted");
// Anthropic key at boundary (sk-ant-xxxxxxxxxx - need at least 18 more chars)
ok(validate_api_key_format("sk-ant-123456789012345678", "anthropic"),
"Anthropic key at 25 character boundary accepted");
}
// ============================================================================
// Main
// ============================================================================
int main() {
// Plan: 61 tests total
// URL validation: 15 tests (9 valid + 6 invalid)
// API key validation: 14 tests
// Numeric range: 13 tests
// Provider name: 8 tests
// Edge cases: 11 tests
plan(61);
test_url_validation();
test_api_key_validation();
test_numeric_range_validation();
test_provider_format_validation();
test_edge_cases();
return exit_status();
}