Add archive helpers and harden task runtime
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
CLI(Request& context)
|
||||
{
|
||||
context.header["Content-Type"] = "text/plain; charset=utf-8";
|
||||
DTree input = cli_input(context);
|
||||
String action = first(input["action"].to_string(), "ping");
|
||||
if(action == "ping")
|
||||
{
|
||||
print("uce-unit-cli: ok\n");
|
||||
print("script=", context.params["SCRIPT_FILENAME"], "\n");
|
||||
return;
|
||||
}
|
||||
if(action == "echo")
|
||||
{
|
||||
print(input["message"].to_string(), "\n");
|
||||
return;
|
||||
}
|
||||
context.set_status(400, "CLI Error");
|
||||
print("unknown cli action: ", action, "\n");
|
||||
}
|
||||
|
||||
RENDER(Request& context)
|
||||
{
|
||||
context.set_status(404, "Not Found");
|
||||
context.header["Content-Type"] = "text/plain; charset=utf-8";
|
||||
print("This unit is intended for local CLI socket invocation.\n");
|
||||
}
|
||||
@@ -55,6 +55,31 @@ RENDER(Request& context)
|
||||
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 & 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>AB</x>")["text"].to_string() == "AB", xml_decode("<x>AB</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";
|
||||
|
||||
@@ -12,6 +12,7 @@ RENDER(Request& context)
|
||||
site_tests_card("units.uce", "Units", "unit_call(), lifecycle hooks, and unit metadata.", "public");
|
||||
site_tests_card("websockets.ws.uce", "WebSockets", "Browser-driven WebSocket helper checks.", "public websocket");
|
||||
site_tests_card("io.uce", "Filesystem", "Filesystem helpers that are restricted outside trusted networks.", "internal");
|
||||
site_tests_card("zip.uce", "ZIP", "Archive helpers that create and extract temporary server-side files.", "internal");
|
||||
site_tests_card("services.uce", "Sockets And Services", "Network/service helpers that are restricted outside trusted networks.", "internal");
|
||||
site_tests_card("tasks.uce", "Tasks", "Background task helper coverage.", "internal");
|
||||
?></div><?
|
||||
|
||||
+19
-1
@@ -31,7 +31,7 @@ RENDER(Request& context)
|
||||
if(mode != "stop")
|
||||
{
|
||||
short_pid = task("site-tests-short", []() {
|
||||
sleep(2);
|
||||
sleep(5);
|
||||
});
|
||||
|
||||
repeat_pid = task_repeat("site-tests-repeat", 1.0, []() {
|
||||
@@ -39,8 +39,23 @@ RENDER(Request& context)
|
||||
}, 4);
|
||||
}
|
||||
|
||||
pid_t timeout_pid = 0;
|
||||
pid_t unsafe_key_pid = 0;
|
||||
if(mode != "stop")
|
||||
{
|
||||
timeout_pid = task("site-tests-timeout", []() {
|
||||
sleep(5);
|
||||
}, 1);
|
||||
unsafe_key_pid = task("site-tests/../unsafe key", []() {
|
||||
sleep(5);
|
||||
});
|
||||
sleep(2);
|
||||
}
|
||||
|
||||
pid_t seen_short_pid = task_pid("site-tests-short");
|
||||
pid_t seen_repeat_pid = task_pid("site-tests-repeat");
|
||||
pid_t seen_timeout_pid = task_pid("site-tests-timeout");
|
||||
pid_t seen_unsafe_key_pid = task_pid("site-tests/../unsafe key");
|
||||
int short_alive = seen_short_pid == 0 ? -1 : task_kill(seen_short_pid, 0);
|
||||
int repeat_alive = seen_repeat_pid == 0 ? -1 : task_kill(seen_repeat_pid, 0);
|
||||
|
||||
@@ -57,6 +72,9 @@ RENDER(Request& context)
|
||||
check("task_pid() + task_kill(pid, 0)", short_alive == 0, "kill(0) result=" + std::to_string(short_alive));
|
||||
check("task_repeat()", repeat_pid != 0 && seen_repeat_pid != 0, "started=" + std::to_string(repeat_pid) + " seen=" + std::to_string(seen_repeat_pid));
|
||||
check("repeat worker liveness", repeat_alive == 0, "kill(0) result=" + std::to_string(repeat_alive));
|
||||
check("task() timeout", timeout_pid != 0 && seen_timeout_pid == 0, "started=" + std::to_string(timeout_pid) + " seen_after_timeout=" + std::to_string(seen_timeout_pid));
|
||||
check("task key normalization", unsafe_key_pid != 0 && seen_unsafe_key_pid != 0, "started=" + std::to_string(unsafe_key_pid) + " seen=" + std::to_string(seen_unsafe_key_pid));
|
||||
check("task_kill() rejects negative pid", task_kill(-1, 0) == -1, "kill(-1, 0) rejected");
|
||||
}
|
||||
|
||||
?><div class="tests-section">
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
#include "testlib.h"
|
||||
|
||||
RENDER(Request& context)
|
||||
{
|
||||
if(!test_demo_request_allowed(context))
|
||||
{
|
||||
site_tests_restricted(context, "ZIP", "create and extract temporary server-side archive files");
|
||||
return;
|
||||
}
|
||||
|
||||
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("ZIP", "Local-only coverage for zip_create(), zip_list(), zip_read(), and zip_extract().");
|
||||
|
||||
String base = "/tmp/uce-site-tests-zip";
|
||||
String archive = path_join(base, "sample.zip");
|
||||
String extract_dir = path_join(base, "extract");
|
||||
mkdir(base);
|
||||
mkdir(extract_dir);
|
||||
|
||||
DTree entries;
|
||||
entries["hello.txt"] = "Hello ZIP";
|
||||
entries["nested/readme.txt"] = "Nested file";
|
||||
DTree explicit_entry;
|
||||
explicit_entry["name"] = "data/value.txt";
|
||||
explicit_entry["content"] = "42";
|
||||
entries["ignored-map-key"] = explicit_entry;
|
||||
|
||||
bool created = zip_create(archive, entries);
|
||||
DTree listed = zip_list(archive);
|
||||
String hello = zip_read(archive, "hello.txt");
|
||||
bool extracted = zip_extract(archive, extract_dir);
|
||||
String nested = file_get_contents(path_join(extract_dir, "nested/readme.txt"));
|
||||
String value = file_get_contents(path_join(extract_dir, "data/value.txt"));
|
||||
|
||||
check("zip_create()", created && file_exists(archive), archive);
|
||||
check("zip_list()", listed["count"].to_u64() == 3 && listed["entries"]["0"]["name"].to_string() != "", json_encode(listed));
|
||||
check("zip_read()", hello == "Hello ZIP", hello);
|
||||
check("zip_extract()", extracted && nested == "Nested file" && value == "42", nested + " / " + value);
|
||||
|
||||
bool unsafe_rejected = false;
|
||||
try
|
||||
{
|
||||
DTree unsafe_entries;
|
||||
unsafe_entries["/absolute.txt"] = "bad";
|
||||
zip_create(path_join(base, "unsafe.zip"), unsafe_entries);
|
||||
}
|
||||
catch(std::exception& e)
|
||||
{
|
||||
unsafe_rejected = contains(e.what(), "unsafe");
|
||||
}
|
||||
check("zip_create() rejects unsafe names", unsafe_rejected, "absolute member name rejected");
|
||||
|
||||
String gz_source = "UCE gzip payload\nline two\n";
|
||||
String gz_body = gz_compress(gz_source);
|
||||
String gz_roundtrip = gz_uncompress(gz_body);
|
||||
check("gz_compress()", gz_body.size() > gz_source.size() && (u8)gz_body[0] == 0x1f && (u8)gz_body[1] == 0x8b, "bytes=" + std::to_string((u64)gz_body.size()));
|
||||
check("gz_uncompress()", gz_roundtrip == gz_source, gz_roundtrip);
|
||||
|
||||
bool bad_gz_rejected = false;
|
||||
try
|
||||
{
|
||||
gz_uncompress("not gzip data");
|
||||
}
|
||||
catch(std::exception& e)
|
||||
{
|
||||
bad_gz_rejected = contains(e.what(), "gz_uncompress");
|
||||
}
|
||||
check("gz_uncompress() rejects invalid data", bad_gz_rejected, "invalid stream rejected");
|
||||
|
||||
site_tests_summary(passed, failed, skipped, "ZIP tests write only under /tmp/uce-site-tests-zip.");
|
||||
site_tests_page_end();
|
||||
}
|
||||
Reference in New Issue
Block a user