#include "nocal/sync/caldav_xml.hpp" #include #include #include #include #include namespace { void require(bool condition, std::string_view message) { if (!condition) { throw std::runtime_error(std::string(message)); } } void test_simple_element() { const auto root = nocal::sync::parse_xml("text"); require(root.tag == "root", "root tag"); require(root.children.size() == 1, "one child"); require(root.children[0].tag == "child", "child tag"); require(root.children[0].text == "text", "child text"); } void test_namespace_prefix() { const auto root = nocal::sync::parse_xml(""); require(root.tag == "D:propfind", "namespaced root"); require(root.children[0].tag == "D:prop", "namespaced child"); require(root.children[0].children[0].tag == "C:displayname", "nested namespaced"); } void test_attributes() { const auto root = nocal::sync::parse_xml(R"()"); require(root.tag == "elem", "tag"); require(root.attributes.size() == 2, "two attributes"); require(nocal::sync::get_attribute(root, "href") == "/path", "href"); require(nocal::sync::get_attribute(root, "name") == "test", "name"); } void test_cdata() { const auto root = nocal::sync::parse_xml("content]]>"); require(root.cdata == "content", "CDATA content preserved"); } void test_self_closing() { const auto root = nocal::sync::parse_xml(""); require(root.children.size() == 2, "two self-closing children"); require(root.children[0].children.empty(), "no nested children in self-closing"); } void test_nested_elements() { const auto root = nocal::sync::parse_xml("deep"); require(root.children[0].children[0].text == "deep", "deep nesting"); } void test_entity_references() { const auto root = nocal::sync::parse_xml("<hello> & world"); require(root.text == " & world", "entities replaced"); } void test_processing_instruction() { const auto root = nocal::sync::parse_xml(""); require(root.tag == "doc", "PI skipped, root found"); } void test_comment_skipped() { const auto root = nocal::sync::parse_xml(""); require(root.tag == "doc", "comment skipped"); } void test_find_children() { const auto root = nocal::sync::parse_xml("123"); const auto found = nocal::sync::find_children(root, "a"); require(found.size() == 2, "two 'a' children"); require(found[0]->text == "1", "first a"); require(found[1]->text == "3", "second a"); } void test_get_child_text() { const auto root = nocal::sync::parse_xml("Bob"); require(nocal::sync::get_child_text(root, "name") == "Bob", "child text"); require(nocal::sync::get_child_text(root, "missing").empty(), "missing child"); } void test_empty_document() { bool threw = false; try { (void)nocal::sync::parse_xml(""); } catch (const std::runtime_error&) { threw = true; } require(threw, "empty document throws"); } void test_unclosed_tag() { bool threw = false; try { (void)nocal::sync::parse_xml(""); } catch (const std::runtime_error&) { threw = true; } require(threw, "unclosed tag throws"); } void test_mismatched_tag() { bool threw = false; try { (void)nocal::sync::parse_xml(""); } catch (const std::runtime_error&) { threw = true; } require(threw, "mismatched tag throws"); } void test_max_depth() { bool threw = false; std::string deep(""); for (int i = 0; i < 100; ++i) { deep += ""; } deep += "x"; for (int i = 0; i < 100; ++i) { deep += ""; } deep += ""; try { (void)nocal::sync::parse_xml(deep); } catch (const std::runtime_error&) { threw = true; } require(threw, "excessive depth throws"); } void test_caldav_propfind_response() { const std::string doc = R"( /calendars/user/calendar1/ HTTP/1.1 200 OK My Calendar sync-token-123 )"; const auto root = nocal::sync::parse_xml(doc); require(root.tag == "D:multistatus", "root tag"); require(root.children.size() == 1, "one response"); const auto& response = root.children[0]; require(response.tag == "D:response", "response element"); const auto hrefs = nocal::sync::find_children(response, "D:href"); require(hrefs.size() == 1 && hrefs[0]->text == "/calendars/user/calendar1/", "href"); // D:displayname is inside D:propstat > D:prop, so traverse down const auto& propstat = response.children[1]; require(propstat.tag == "D:propstat", "propstat element"); const auto& prop = propstat.children[1]; require(prop.tag == "D:prop", "prop element"); const auto displaynames = nocal::sync::find_children(prop, "D:displayname"); require(displaynames.size() == 1 && displaynames[0]->text == "My Calendar", "displayname"); const auto sync_tokens = nocal::sync::find_children(prop, "D:sync-token"); require(sync_tokens.size() == 1 && sync_tokens[0]->text == "sync-token-123", "sync-token"); } void test_max_size() { bool threw = false; try { const std::string large(5 * 1024 * 1024, 'x'); (void)nocal::sync::parse_xml("" + large + "", 1024); } catch (const std::runtime_error&) { threw = true; } require(threw, "oversized document throws"); } } // namespace int main() { try { test_simple_element(); test_namespace_prefix(); test_attributes(); test_cdata(); test_self_closing(); test_nested_elements(); test_entity_references(); test_processing_instruction(); test_comment_skipped(); test_find_children(); test_get_child_text(); test_empty_document(); test_unclosed_tag(); test_mismatched_tag(); test_max_depth(); test_caldav_propfind_response(); test_max_size(); std::cout << "caldav_xml_tests: ok\n"; return 0; } catch (const std::exception& error) { std::cerr << "caldav_xml_tests: " << error.what() << '\n'; return 1; } }