uce/site/tests/core.uce

144 lines
10 KiB
Plaintext

#include "testlib.h"
RENDER(Request& context)
{
u64 passed = 0;
u64 failed = 0;
u64 skipped = 0;
auto check = [&](String name, bool ok, String detail)
{
site_tests_case(name, ok ? "pass" : "fail", detail);
if(ok)
passed++;
else
failed++;
};
site_tests_page_start("Core APIs", "Pure helper coverage for strings, UTF-8 splitting, DTree, and JSON encoding/decoding.");
auto comma_parts = split("alpha,beta,gamma", ",");
auto empty_delim_parts = split("alpha", "");
check("split()", comma_parts.size() == 3 && comma_parts[1] == "beta" && empty_delim_parts.size() == 1 && empty_delim_parts[0] == "alpha", join(comma_parts, " | "));
auto spaced_parts = split_space(" one two three ");
check("split_space()", spaced_parts.size() == 3 && spaced_parts[2] == "three", join(spaced_parts, " / "));
StringMap kv = split_kv("\n#comment\nalpha = one\nempty=\n", '=', true, false);
StringMap http_headers = split_http_headers("GET /demo.uce?x=1 HTTP/1.1\r\nHost: example.test\r\nX-Empty:\r\n");
StringMap leading_crlf_headers = split_http_headers("\r\nGET /lead.uce HTTP/1.1\r\nHost: lead.example\r\n");
StringMap header_only = split_http_headers("Host: example.test\nX-Token: abc\n");
check("trim() / split_kv() / split_http_headers()", trim(" padded value ") == "padded value" && kv["alpha"] == "one" && kv["empty"] == "" && http_headers["REQUEST_METHOD"] == "GET" && http_headers["DOCUMENT_URI"] == "/demo.uce" && http_headers["QUERY_STRING"] == "x=1" && http_headers["HTTP_X_EMPTY"] == "" && leading_crlf_headers["REQUEST_METHOD"] == "GET" && leading_crlf_headers["DOCUMENT_URI"] == "/lead.uce" && leading_crlf_headers["HTTP_HOST"] == "lead.example" && header_only["REQUEST_METHOD"] == "" && header_only["HTTP_HOST"] == "example.test" && header_only["HTTP_X_TOKEN"] == "abc", trim(" padded value ") + " / " + var_dump(kv) + " / " + var_dump(http_headers) + " / " + var_dump(leading_crlf_headers) + " / " + var_dump(header_only));
check("replace()", replace("alpha-beta-beta", "beta", "done") == "alpha-done-done", replace("alpha-beta-beta", "beta", "done"));
check("html_escape() attribute-safe quotes", html_escape("<&>\"Don't") == "&lt;&amp;&gt;&quot;Don&#39;t", html_escape("<&>\"Don't"));
check("regex_match()", regex_match("[A-Z][a-z]+", "Alice") && !regex_match("[A-Z][a-z]+", "Alice!"), "full-string validation");
DTree regex_email = regex_search("(?<user>[A-Za-z0-9._%+-]+)@(?<host>[A-Za-z0-9.-]+)", "Contact ops@example.test");
check("regex_search()", regex_email["matched"].to_bool() && regex_email["named"]["user"].to_string() == "ops" && regex_email["named"]["host"].to_string() == "example.test", json_encode(regex_email));
DTree regex_tags = regex_search_all("#(?<tag>[A-Za-z0-9_]+)", "#uce #docs");
check("regex_search_all()", regex_tags["count"].to_s64() == 2 && regex_tags["matches"]["1"]["named"]["tag"].to_string() == "docs", json_encode(regex_tags));
check("regex_replace()", regex_replace("#([A-Za-z0-9_]+)", "<tag>$1</tag>", "#uce") == "<tag>uce</tag>", regex_replace("#([A-Za-z0-9_]+)", "<tag>$1</tag>", "#uce"));
auto regex_parts = regex_split("\\s*,\\s*", "uce, components, markdown");
check("regex_split()", regex_parts.size() == 3 && regex_parts[1] == "components", join(regex_parts, " | "));
check("substr() + strpos()", strpos("component suite", "suite") == 10 && substr("component suite", 10) == "suite", "strpos=10 substr='" + substr("component suite", 10) + "'");
check("str_starts_with()", str_starts_with("websocket-suite", "websocket"), "websocket-suite starts with websocket");
check("str_ends_with()", str_ends_with("component.uce", ".uce"), "component.uce ends with .uce");
check("to_lower() / to_upper()", to_lower("MiXeD") == "mixed" && to_upper("MiXeD") == "MIXED", to_lower("MiXeD") + " / " + to_upper("MiXeD"));
check("request context params", context.params["SCRIPT_URL"] != "" && context.params["BASE_URL"] != "" && context.params["ROUTE_PATH"] != "" && context.params["ROUTE_PAGE"] != "" && context.params["ROUTE_VALID"] == "1", "script=" + context.params["SCRIPT_URL"] + " base=" + context.params["BASE_URL"] + " route=" + context.params["ROUTE_PATH"] + " page=" + context.params["ROUTE_PAGE"] + " valid=" + context.params["ROUTE_VALID"]);
String saved_query_string = context.params["QUERY_STRING"];
context.params["QUERY_STRING"] = "workspace/projects&theme=dark";
check("request_query_path() delegates to request_query_route()", request_query_path(context) == request_query_route(context)["l_path"].to_string() && request_query_path(context) == "workspace/projects", request_query_path(context) + " / " + json_encode(request_query_route(context)));
context.params["QUERY_STRING"] = saved_query_string;
check("route path sanitizers", route_path_normalize("/workspace/projects/") == "workspace/projects" && route_path_is_safe("workspace/projects") && !route_path_is_safe("../demo") && !route_path_is_safe("workspace/../demo") && !route_path_is_safe("workspace/file.uce") && route_path_sanitize("../demo") == "" && route_path_sanitize("") == "index", route_path_sanitize("/workspace/projects/"));
StringList route_parts = {"dashboard", "index", "dashboard", "themes"};
auto unique_routes = list_unique(route_parts);
auto sorted_routes = list_sort(unique_routes);
auto upper_routes = map(sorted_routes, [](String item) { return(to_upper(item)); });
auto dashboard_routes = filter(route_parts, [](String item) { return(item == "dashboard"); });
check("map/filter/list_unique/sort/find/some/every", unique_routes.size() == 3 && sorted_routes[0] == "dashboard" && upper_routes[2] == "THEMES" && dashboard_routes.size() == 2 && list_find(route_parts, [](String item) { return(str_starts_with(item, "them")); }, "missing") == "themes" && list_some(route_parts, [](String item) { return(item == "index"); }) && list_every(unique_routes, [](String item) { return(item != ""); }), join(upper_routes, ","));
DTree nav;
DTree nav_home;
nav_home["title"] = "Home";
nav_home["section"] = "main";
nav.push(nav_home);
DTree nav_dash;
nav_dash["title"] = "Dashboard";
nav_dash["section"] = "app";
nav.push(nav_dash);
DTree nav_themes;
nav_themes["title"] = "Themes";
nav_themes["section"] = "app";
nav.push(nav_themes);
DTree empty_tree;
DTree empty_pop = empty_tree.pop();
DTree app_nav = dtree_filter(nav, [](DTree item, String key) { return(item["section"].to_string() == "app"); });
DTree nav_titles = dtree_map(app_nav, [](DTree item, String key) { DTree title; title = item["title"].to_string(); return(title); });
DTree grouped_nav = dtree_group_by(nav, [](DTree item, String key) { return(item["section"].to_string()); });
DTree nav_dash_summary = dtree_pick(nav_dash, {"title"});
DTree nav_dash_public = dtree_omit(nav_dash, {"section"});
check("DTree collection helpers", empty_pop.to_string() == "" && app_nav.is_list() && nav_titles["0"].to_string() == "Dashboard" && grouped_nav["app"]["1"]["title"].to_string() == "Themes" && join(dtree_keys(nav_dash_summary), ",") == "title" && dtree_values(nav_dash_public)["0"].to_string() == "Dashboard", json_encode(grouped_nav));
String binary_payload = "core";
binary_payload.push_back((char)0x00);
binary_payload.push_back((char)0xff);
binary_payload += "payload";
bool base64_ok = false;
String encoded_base64 = base64_encode(binary_payload);
String decoded_base64 = base64_decode(encoded_base64, base64_ok);
bool invalid_base64_ok = true;
base64_decode("AA=A", invalid_base64_ok);
check("base64_encode() / base64_decode() binary-safe", base64_ok && decoded_base64 == binary_payload && decoded_base64.size() == binary_payload.size() && !invalid_base64_ok, encoded_base64 + " bytes=" + std::to_string((u64)decoded_base64.size()));
String utf8_sample = "A\xC3\xA9";
auto utf8_parts = split_utf8(utf8_sample);
check("split_utf8()", utf8_parts.size() == 2, "count=" + std::to_string(utf8_parts.size()));
DTree payload;
payload["name"] = "uce";
payload["count"] = (f64)3;
payload["kind"] = "core";
String payload_json = json_encode(payload);
DTree decoded = json_decode(payload_json);
check("json_encode() / json_decode()", decoded["name"].to_string() == "uce" && int_val(decoded["count"].to_string()) == 3, payload_json);
DTree xml_doc;
xml_doc["name"] = "book";
xml_doc["attrs"]["id"] = "b1";
DTree xml_title;
xml_title["name"] = "title";
xml_title["text"] = "UCE & XML";
xml_doc["children"].push(xml_title);
String encoded_xml = xml_encode(xml_doc);
DTree decoded_xml = xml_decode(encoded_xml);
check("xml_encode() / xml_decode()", contains(encoded_xml, "id=\"b1\"") && contains(encoded_xml, "UCE &amp; XML") && decoded_xml["children"]["0"]["text"].to_string() == "UCE & XML", encoded_xml);
DTree xml_payload;
xml_payload["title"] = "Hello";
xml_payload["count"] = "3";
check("xml_encode() simple map", xml_encode(xml_payload, "payload") == "<payload><count>3</count><title>Hello</title></payload>", xml_encode(xml_payload, "payload"));
check("xml_decode() numeric entities", xml_decode("<x>&#x41;&#66;</x>")["text"].to_string() == "AB", xml_decode("<x>&#x41;&#66;</x>")["text"].to_string());
String yaml_config = "app:\n name: UCE Starter\n debug: true\n port: 8080\n paths:\n - site\n - cache\nmessage: |\n hello\n world\n";
DTree decoded_yaml = yaml_decode(yaml_config);
check("yaml_decode() config", decoded_yaml["app"]["name"].to_string() == "UCE Starter" && decoded_yaml["app"]["debug"].to_bool() && decoded_yaml["app"]["port"].to_s64() == 8080 && decoded_yaml["app"]["paths"]["1"].to_string() == "cache" && decoded_yaml["message"].to_string() == "hello\nworld", json_encode(decoded_yaml));
String encoded_yaml = yaml_encode(decoded_yaml);
DTree yaml_roundtrip = yaml_decode(encoded_yaml);
check("yaml_encode() / yaml_decode()", contains(encoded_yaml, "app:") && contains(encoded_yaml, "paths:") && yaml_roundtrip["app"]["debug"].to_bool() && yaml_roundtrip["message"].to_string() == "hello\nworld", encoded_yaml);
DTree tree;
tree["suite"] = "core";
tree["nested"]["api"] = "dtree";
tree["nested"]["ok"].set_bool(true);
check("DTree map access", tree["suite"].to_string() == "core" && tree["nested"]["api"].to_string() == "dtree", json_encode(tree));
site_tests_summary(passed, failed, skipped, "These assertions intentionally stay pure and side-effect free so they remain safe on the public site.");
site_tests_page_end();
}