# Test for input validation functions # Comprehensive tests for all validation and sanitization functions # ============================================================================ # validate_path() tests # ============================================================================ test_that("validate_path accepts valid paths", { result <- validate_path("/valid/path/to/file.txt") expect_true(result$valid) expect_equal(result$sanitized, "/valid/path/to/file.txt") result <- validate_path("simple.txt") expect_true(result$valid) expect_equal(result$sanitized, "simple.txt") }) test_that("validate_path rejects null and invalid types", { result <- validate_path(NULL) expect_false(result$valid) expect_match(result$error, "Invalid path type") result <- validate_path(123) expect_false(result$valid) expect_match(result$error, "Invalid path type") result <- validate_path(c("path1", "path2")) expect_false(result$valid) expect_match(result$error, "Invalid path type") }) test_that("validate_path rejects paths that are too long", { long_path <- paste0(rep("a", 1001), collapse = "") result <- validate_path(long_path, max_length = 1000) expect_false(result$valid) expect_match(result$error, "Path too long") # Just under the limit should be OK ok_path <- paste0(rep("a", 1000), collapse = "") result <- validate_path(ok_path, max_length = 1000) expect_true(result$valid) }) test_that("validate_path detects path traversal attacks", { result <- validate_path("../../../etc/passwd") expect_false(result$valid) expect_match(result$error, "Path traversal") result <- validate_path("/safe/path/../../etc/passwd") expect_false(result$valid) expect_match(result$error, "Path traversal") result <- validate_path("..\\..\\windows\\system32") expect_false(result$valid) expect_match(result$error, "Path traversal") # ".." by itself or at the end is OK (handled by patterns) result <- validate_path("valid/path..") expect_true(result$valid) }) test_that("validate_path rejects dangerous control characters", { # SOH (Start of Heading) path_soh <- paste0("path", rawToChar(as.raw(1)), "invalid") result <- validate_path(path_soh) expect_false(result$valid) expect_match(result$error, "invalid characters") # US (Unit Separator) path_us <- paste0("path", rawToChar(as.raw(31)), "invalid") result <- validate_path(path_us) expect_false(result$valid) expect_match(result$error, "invalid characters") # DEL path_del <- paste0("path", rawToChar(as.raw(127)), "invalid") result <- validate_path(path_del) expect_false(result$valid) expect_match(result$error, "invalid characters") }) # ============================================================================ # validate_http_method() tests # ============================================================================ test_that("validate_http_method accepts valid methods", { methods <- c("GET", "POST", "PUT", "DELETE", "HEAD", "OPTIONS") for (method in methods) { result <- validate_http_method(method) expect_true(result$valid) expect_equal(result$sanitized, method) } }) test_that("validate_http_method converts to uppercase and trims", { result <- validate_http_method(" get ") expect_true(result$valid) expect_equal(result$sanitized, "GET") result <- validate_http_method("post") expect_true(result$valid) expect_equal(result$sanitized, "POST") }) test_that("validate_http_method rejects null and invalid types", { result <- validate_http_method(NULL) expect_false(result$valid) expect_match(result$error, "Invalid method type") result <- validate_http_method(123) expect_false(result$valid) expect_match(result$error, "Invalid method type") result <- validate_http_method(c("GET", "POST")) expect_false(result$valid) expect_match(result$error, "Invalid method type") }) test_that("validate_http_method rejects invalid methods", { result <- validate_http_method("INVALID") expect_false(result$valid) expect_match(result$error, "not allowed") result <- validate_http_method("TRACE") expect_false(result$valid) expect_match(result$error, "not allowed") }) # ============================================================================ # validate_query_string() tests # ============================================================================ test_that("validate_query_string accepts valid query strings", { result <- validate_query_string("key=value&foo=bar") expect_true(result$valid) expect_equal(result$sanitized, "key=value&foo=bar") result <- validate_query_string("name=John%20Doe&age=25") expect_true(result$valid) }) test_that("validate_query_string handles null as empty string", { result <- validate_query_string(NULL) expect_true(result$valid) expect_equal(result$sanitized, "") }) test_that("validate_query_string rejects invalid types", { result <- validate_query_string(123) expect_false(result$valid) expect_match(result$error, "Invalid query string type") result <- validate_query_string(c("a=1", "b=2")) expect_false(result$valid) expect_match(result$error, "Invalid query string type") }) test_that("validate_query_string rejects strings that are too long", { long_query <- paste0(rep("a", 2049), collapse = "") result <- validate_query_string(long_query, max_length = 2048) expect_false(result$valid) expect_match(result$error, "too long") }) test_that("validate_query_string validates URL encoding", { # Invalid: % followed by non-hex result <- validate_query_string("key=%ZZ") expect_false(result$valid) expect_match(result$error, "Invalid URL encoding") # Invalid: % followed by only one hex digit result <- validate_query_string("key=%2G") expect_false(result$valid) expect_match(result$error, "Invalid URL encoding") # Valid: proper encoding result <- validate_query_string("key=%20%2F%3D") expect_true(result$valid) }) # ============================================================================ # validate_app_name() tests # ============================================================================ test_that("validate_app_name accepts valid names", { valid_names <- c("myapp", "my-app", "my_app", "MyApp123", "app-1_test") for (name in valid_names) { result <- validate_app_name(name) expect_true(result$valid, info = paste("Failed for:", name)) expect_equal(result$sanitized, name) } }) test_that("validate_app_name rejects null and invalid types", { result <- validate_app_name(NULL) expect_false(result$valid) expect_match(result$error, "Invalid app name type") result <- validate_app_name(123) expect_false(result$valid) expect_match(result$error, "Invalid app name type") result <- validate_app_name(c("app1", "app2")) expect_false(result$valid) expect_match(result$error, "Invalid app name type") }) test_that("validate_app_name rejects invalid characters", { invalid_names <- c("my app", "my.app", "my@app", "my/app", "my\\app", "my!app") for (name in invalid_names) { result <- validate_app_name(name) expect_false(result$valid, info = paste("Should reject:", name)) expect_match(result$error, "invalid characters") } }) test_that("validate_app_name rejects names that are too long", { long_name <- paste0(rep("a", 51), collapse = "") result <- validate_app_name(long_name, max_length = 50) expect_false(result$valid) expect_match(result$error, "too long") # At the limit should be OK ok_name <- paste0(rep("a", 50), collapse = "") result <- validate_app_name(ok_name, max_length = 50) expect_true(result$valid) }) # ============================================================================ # validate_port() tests # ============================================================================ test_that("validate_port accepts valid port numbers", { valid_ports <- c(1, 80, 443, 8080, 3000, 65535) for (port in valid_ports) { result <- validate_port(port) expect_true(result$valid, info = paste("Failed for:", port)) expect_equal(result$sanitized, as.integer(port)) } }) test_that("validate_port rejects null", { result <- validate_port(NULL) expect_false(result$valid) expect_match(result$error, "null") }) test_that("validate_port rejects non-numeric values", { result <- validate_port("8080") expect_false(result$valid) expect_match(result$error, "must be a number") result <- validate_port(c(80, 443)) expect_false(result$valid) expect_match(result$error, "must be a number") }) test_that("validate_port rejects out-of-range ports", { result <- validate_port(0) expect_false(result$valid) expect_match(result$error, "between 1 and 65535") result <- validate_port(-1) expect_false(result$valid) expect_match(result$error, "between 1 and 65535") result <- validate_port(65536) expect_false(result$valid) expect_match(result$error, "between 1 and 65535") }) # ============================================================================ # validate_session_id() tests # ============================================================================ test_that("validate_session_id accepts valid session IDs", { # Valid 64-character hex string (simulating SHA-256) valid_id <- paste0(rep("a", 64), collapse = "") result <- validate_session_id(valid_id) expect_true(result$valid) expect_equal(result$sanitized, valid_id) # Mixed case hex mixed_id <- paste0(c(rep("A", 32), rep("f", 32)), collapse = "") result <- validate_session_id(mixed_id) expect_true(result$valid) }) test_that("validate_session_id rejects null and invalid types", { result <- validate_session_id(NULL) expect_false(result$valid) expect_match(result$error, "Invalid session ID type") result <- validate_session_id(123) expect_false(result$valid) expect_match(result$error, "Invalid session ID type") result <- validate_session_id(c("abc", "def")) expect_false(result$valid) expect_match(result$error, "Invalid session ID type") }) test_that("validate_session_id rejects invalid formats", { # Wrong length result <- validate_session_id(paste0(rep("a", 63), collapse = "")) expect_false(result$valid) expect_match(result$error, "Invalid session ID format") result <- validate_session_id(paste0(rep("a", 65), collapse = "")) expect_false(result$valid) expect_match(result$error, "Invalid session ID format") # Non-hex characters result <- validate_session_id(paste0(c(rep("a", 63), "z"), collapse = "")) expect_false(result$valid) expect_match(result$error, "Invalid session ID format") }) # ============================================================================ # html_escape() tests # ============================================================================ test_that("html_escape escapes dangerous characters", { result <- html_escape("") expect_equal(result, "<script>alert('xss')</script>") result <- html_escape('Test & "quotes" and ') expect_equal(result, "Test & "quotes" and <tags>") result <- html_escape("She said 'hello' & waved") expect_equal(result, "She said 'hello' & waved") }) test_that("html_escape handles null and non-character input", { result <- html_escape(NULL) expect_equal(result, "") result <- html_escape(123) expect_equal(result, "") result <- html_escape(list(a = 1)) expect_equal(result, "") }) test_that("html_escape preserves safe characters", { result <- html_escape("safe text 123") expect_equal(result, "safe text 123") }) # ============================================================================ # sanitize_for_logging() tests # ============================================================================ test_that("sanitize_for_logging truncates long text", { long_text <- paste0(rep("a", 150), collapse = "") result <- sanitize_for_logging(long_text, max_length = 100) expect_equal(nchar(result), 103) expect_match(result, "\\.\\.\\.$") }) test_that("sanitize_for_logging removes control characters", { text_with_control <- paste0("text", rawToChar(as.raw(1)), "with", rawToChar(as.raw(31)), "control", rawToChar(as.raw(127)), "chars") result <- sanitize_for_logging(text_with_control) expect_equal(result, "text?with?control?chars") result <- sanitize_for_logging("normal\nnewline\ttab") expect_equal(result, "normal?newline?tab") }) test_that("sanitize_for_logging handles null and non-character input", { result <- sanitize_for_logging(NULL) expect_equal(result, "(null)") result <- sanitize_for_logging(123) expect_equal(result, "(null)") result <- sanitize_for_logging(list(a = 1)) expect_equal(result, "(null)") }) # ============================================================================ # create_validation_error() tests # ============================================================================ test_that("create_validation_error creates proper error structure", { result <- create_validation_error("Test error") expect_false(result$valid) expect_equal(result$error, "Test error") expect_equal(result$status_code, 400) expect_equal(result$response$status, 400) expect_equal(result$response$headers$`Content-Type`, "application/json") expect_match(result$response$body, "Test error") }) test_that("create_validation_error respects custom status codes", { result <- create_validation_error("Forbidden", status_code = 403) expect_equal(result$status_code, 403) expect_equal(result$response$status, 403) expect_match(result$response$body, "403") }) # ============================================================================ # validate_request_inputs() tests # ============================================================================ test_that("validate_request_inputs validates all inputs in batch", { result <- validate_request_inputs("/valid/path", "GET", "key=value") expect_true(result$valid) expect_equal(result$path, "/valid/path") expect_equal(result$method, "GET") expect_equal(result$query_string, "key=value") }) test_that("validate_request_inputs works without query string", { result <- validate_request_inputs("/valid/path", "POST", NULL) expect_true(result$valid) expect_equal(result$path, "/valid/path") expect_equal(result$method, "POST") expect_null(result$query_string) }) test_that("validate_request_inputs returns error for invalid path", { result <- validate_request_inputs("../../../etc/passwd", "GET") expect_false(result$valid) expect_match(result$error, "Invalid path") expect_equal(result$status_code, 400) expect_true("response" %in% names(result)) }) test_that("validate_request_inputs returns error for invalid method", { result <- validate_request_inputs("/valid/path", "INVALID") expect_false(result$valid) expect_match(result$error, "Invalid method") expect_equal(result$status_code, 405) })