src/server/plugins/postgresql/pgtest.c

changeset 385
a1f4cb076d2f
parent 374
77506ec632a4
child 403
0f678595d497
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/plugins/postgresql/pgtest.c	Sat Sep 24 16:26:10 2022 +0200
@@ -0,0 +1,911 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2022 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <inttypes.h>
+
+#include "../../util/util.h"
+#include "../../test/testutils.h"
+#include "../../test/webdav.h"
+#include "../../public/nsapi.h"
+#include "../../public/webdav.h"
+#include "../../webdav/webdav.h"
+
+#include <ucx/string.h>
+#include <ucx/utils.h>
+#include <ucx/buffer.h>
+
+#include "pgtest.h"
+#include "vfs.h"
+#include "webdav.h"
+
+#include <libpq-fe.h>
+
+#define xstreq(a,b) xmlStrEqual(BAD_CAST a, BAD_CAST b)
+
+static char *pg_connstr = "postgresql://localhost/test1";
+static int abort_pg_tests = 0;
+static PGconn *test_connection;
+static ResourceData resdata;
+static PgRepository test_repo;
+
+void debug_print_resources(void) {
+    PGresult *result = PQexec(test_connection, "select * from Resource;");
+    int n = PQntuples(result);
+    printf("\nntuples: %d\n-----------------------------------------------\n", n);
+    printf("%10s  %10s  %s\n", "resource_id", "parent_id", "nodename");
+    for(int i=0;i<n;i++) {
+        char *res_id = PQgetvalue(result, i, 0);
+        char *parent_id = PQgetvalue(result, i, 1);
+        char *nodename = PQgetvalue(result, i, 2);
+        printf("%10s  %10s  %s\n", res_id, parent_id, nodename);
+    }
+    printf("\n");
+}
+
+static void test_root_lookup(void) {
+    memset(&test_repo,  0, sizeof(PgRepository));
+    
+    int64_t root_id = -1;
+    int err = pg_lookup_root(&resdata, "root", &root_id);
+    test_repo.root_resource_id = root_id;
+    
+    if(err || root_id < 0) {
+        abort_pg_tests = 1;
+    }
+}
+
+void register_pg_tests(int argc, char **argv, UcxTestSuite *suite) {
+    
+    test_connection = PQconnectdb(pg_connstr);
+    if(!test_connection) {
+        abort_pg_tests = 1;
+    }
+    
+    if(PQstatus(test_connection) != CONNECTION_OK) {
+        abort_pg_tests = 1;
+    }
+    
+    resdata.data = test_connection;
+    test_root_lookup();
+    
+    ucx_test_register(suite, test_pg_conn);
+    if(!abort_pg_tests) {
+        ucx_test_register(suite, test_pg_lookup_root);
+        
+        ucx_test_register(suite, test_pg_vfs_open);
+        ucx_test_register(suite, test_pg_vfs_io);
+        ucx_test_register(suite, test_pg_vfs_stat);
+        ucx_test_register(suite, test_pg_vfs_mkdir);
+        ucx_test_register(suite, test_pg_vfs_unlink);
+        ucx_test_register(suite, test_pg_vfs_rmdir);
+        
+        ucx_test_register(suite, test_pg_webdav_create_from_resdata);
+        ucx_test_register(suite, test_pg_prepare_tests);
+        ucx_test_register(suite, test_pg_webdav_propfind);
+        ucx_test_register(suite, test_pg_webdav_propfind_allprop);
+        ucx_test_register(suite, test_pg_webdav_proppatch_set);
+        
+        PGresult *result = PQexec(test_connection, "BEGIN");
+        PQclear(result);
+    }
+}
+
+static void parse_response_tag(TestMultistatus *ms, xmlNode *node) {
+    // thanks to dav for some of this code
+    UcxAllocator *a = ms->mp->allocator;
+    node = node->children;
+    
+    sstr_t href = {NULL, 0};
+    UcxMap *properties = ucx_map_new_a(ms->mp->allocator, 16);
+    
+    while(node) {
+        if(node->type == XML_ELEMENT_NODE) {
+            if(xstreq(node->name, "href")) {
+                xmlNode *href_node = node->children;
+                if(href_node->type != XML_TEXT_NODE) {
+                    return;
+                }
+                href = sstrdup_a(ms->mp->allocator, scstr((const char*)href_node->content));
+            } else if(xstreq(node->name, "propstat")) {
+                xmlNode *n = node->children;
+                xmlNode *prop_node = NULL;
+                int status_code = 0;
+                while(n) {
+                    if(n->type == XML_ELEMENT_NODE) {
+                        if(xstreq(n->name, "prop")) {
+                            prop_node = n;
+                        } else if(xstreq(n->name, "status")) {
+                            xmlNode *status_node = n->children;
+                            if(status_node->type != XML_TEXT_NODE) {
+                                return;
+                            }
+                            sstr_t status_str = sstr((char*)status_node->content);
+                            if(status_str.length < 13) {
+                                return;
+                            }
+                            status_str = sstrsubsl(status_str, 9, 3);
+                            sstr_t status_s = sstrdup(status_str);
+                            status_code = atoi(status_s.ptr);
+                            free(status_s.ptr);
+                        }
+                    }    
+                    n = n->next;
+                }
+                
+                n = prop_node->children;
+                while(n) {
+                    if(n->type == XML_ELEMENT_NODE) {
+                        TestProperty *property = ucx_mempool_calloc(ms->mp, 1, sizeof(TestProperty));
+                        if(n->ns) {
+                            property->prefix = n->ns->prefix ? sstrdup_a(a, scstr((const char*)n->ns->prefix)).ptr : NULL;
+                            property->namespace = n->ns->href ? sstrdup_a(a, scstr((const char*)n->ns->href)).ptr : NULL;
+                        }
+                        property->name = sstrdup_a(a, scstr((const char*)n->name)).ptr;
+                        property->node = n;
+                        property->status = status_code;
+                        xmlNode *value = n->children;
+                        if(value && value->type == XML_TEXT_NODE) {
+                            property->value = sstrdup_a(a, scstr((const char*)value->content)).ptr;
+                        }
+                        sstr_t pname = sstrcat(2, sstr(property->namespace), sstr(property->name));
+                        ucx_map_sstr_put(properties, pname, property);
+                        free(pname.ptr);
+                    }
+                    n = n->next;
+                }
+            }
+        }
+        node = node->next;
+    }
+    
+    TestResponse *resp =almalloc(a, sizeof(TestResponse));
+    resp->href = href.ptr;
+    resp->properties = properties;
+    
+    ucx_map_sstr_put(ms->responses, href, resp);
+}
+
+TestMultistatus* test_parse_multistatus(const char *space, size_t size) {
+    xmlDoc *doc = xmlReadMemory(space, size, NULL, NULL, 0);
+    if(!doc) {
+        return NULL;
+    }
+    
+    UcxMempool *mp = ucx_mempool_new(64);
+    TestMultistatus *ms = ucx_mempool_malloc(mp, sizeof(TestMultistatus));
+    ms->doc = doc;
+    ms->mp = mp;
+    ms->responses = ucx_map_new_a(mp->allocator, 8);
+    
+    // parse response
+    xmlNode *xml_root = xmlDocGetRootElement(doc);
+    xmlNode *node = xml_root->children;
+    while(node) {
+        if(node->type == XML_ELEMENT_NODE) {
+            if(xstreq(node->name, "response")) {
+                parse_response_tag(ms, node);
+            }
+        }
+        node = node->next;
+    }
+    
+    return ms;
+}
+
+
+void test_multistatus_destroy(TestMultistatus *ms) {
+    if(!ms) return;
+    xmlFreeDoc(ms->doc);
+    ucx_mempool_destroy(ms->mp);
+}
+
+
+UCX_TEST(test_pg_conn) {
+    char *msg = test_connection ? PQerrorMessage(test_connection) : "no connection";
+    
+    UCX_TEST_BEGIN;
+    
+    if(abort_pg_tests) {
+        int msglen = strlen(msg);
+        if(msglen > 0 && msg[msglen-1] == '\n') {
+            msglen--;
+        }
+        fprintf(stdout, "%.*s: ", msglen, msg);
+        UCX_TEST_ASSERT(1 == 0, "skip pg tests");
+    } else {
+        UCX_TEST_ASSERT(1 == 1, "ok");
+    }
+    
+    UCX_TEST_END;
+}
+
+UCX_TEST(test_pg_lookup_root) {
+    UCX_TEST_BEGIN;
+    
+    // test already done in test_root_lookup()
+    UCX_TEST_ASSERT(!abort_pg_tests, "Lookup failed");
+    
+    UCX_TEST_END;
+}
+
+
+static VFS* create_test_pgvfs(Session *sn, Request *rq) { 
+    return pg_vfs_create_from_resourcedata(sn, rq, &test_repo, &resdata);
+}
+
+
+UCX_TEST(test_pg_vfs_open) {
+    Session *sn = testutil_session();
+    Request *rq = testutil_request(sn->pool, "PUT", "/");
+    rq->vfs = create_test_pgvfs(sn, rq);
+    VFSContext *vfs = vfs_request_context(sn, rq);
+    SYS_FILE file;
+    
+    UCX_TEST_BEGIN;
+    
+    file = vfs_open(vfs, "/test_notfound1", O_RDONLY);
+    UCX_TEST_ASSERT(!file, "/test_notfound should not exist");
+    
+    file = vfs_open(vfs, "/test_file1", O_RDWR | O_CREAT);
+    UCX_TEST_ASSERT(file, "cannot create file 1");
+    
+    vfs_close(file);
+    
+    UCX_TEST_END;
+    
+    testutil_destroy_session(sn);
+}
+
+UCX_TEST(test_pg_vfs_io) {
+    Session *sn = testutil_session();
+    Request *rq = testutil_request(sn->pool, "PUT", "/");
+    rq->vfs = create_test_pgvfs(sn, rq);
+    VFSContext *vfs = vfs_request_context(sn, rq);
+    SYS_FILE file;
+    SYS_FILE file2;
+    
+    UCX_TEST_BEGIN;
+    
+    file = vfs_open(vfs, "/test_f1", O_WRONLY | O_CREAT);
+    UCX_TEST_ASSERT(file, "cannot open file1");
+    
+    int w = system_fwrite(file, "test1\n", 6);
+    UCX_TEST_ASSERT(w == 6, "fwrite ret (1)");
+    w = system_fwrite(file, "2", 1);
+    UCX_TEST_ASSERT(w == 1, "fwrite ret (2)");
+    
+    vfs_close(file);
+    
+    file = vfs_open(vfs, "/test_f1", O_RDONLY);
+    file2 = vfs_open(vfs, "/test_f2", O_WRONLY | O_CREAT);
+    UCX_TEST_ASSERT(file, "cannot open file1");
+    UCX_TEST_ASSERT(file2, "cannot open file2");
+    
+    char buf[128];
+    int r = system_fread(file, buf, 128);
+    UCX_TEST_ASSERT(r == 7, "cannot read from file1");
+    
+    w = system_fwrite(file2, buf, r);
+    UCX_TEST_ASSERT(w == 7, "cannot write to file2");
+    
+    vfs_close(file);
+    vfs_close(file2);
+    
+    file2 = vfs_open(vfs, "/test_f2", O_RDONLY);
+    
+    r = system_fread(file, buf, 128);
+    UCX_TEST_ASSERT(r == 7, "fread ret");
+    UCX_TEST_ASSERT(!memcmp(buf, "test1\n2", 7), "wrong buffer content after read");
+    
+    vfs_close(file2);
+    
+    
+    UCX_TEST_END;
+    
+    testutil_destroy_session(sn);
+}
+
+UCX_TEST(test_pg_vfs_stat) {
+    Session *sn = testutil_session();
+    Request *rq = testutil_request(sn->pool, "PUT", "/");
+    rq->vfs = create_test_pgvfs(sn, rq);
+    VFSContext *vfs = vfs_request_context(sn, rq);
+    
+    UCX_TEST_BEGIN;
+    
+    // testdata, content doesn't matter
+    char test1[512];
+    memset(test1, 'x', 512);
+    const int test_len1 = 200;
+    const int test_len2 = 432;
+    
+    SYS_FILE f1 = vfs_open(vfs, "/test_s1", O_WRONLY|O_CREAT);
+    UCX_TEST_ASSERT(f1, "cannot open test_s1");
+    system_fwrite(f1, test1, test_len1);
+    vfs_close(f1);
+    
+    SYS_FILE f2 = vfs_open(vfs, "/test_s2", O_RDWR|O_CREAT);
+    UCX_TEST_ASSERT(f2, "cannot open test_s2");
+    system_fwrite(f2, test1, test_len2);
+    vfs_close(f2);
+    
+    struct stat st1, st2;
+    int r1 = vfs_stat(vfs, "/test_s1", &st1);
+    int r2 = vfs_stat(vfs, "/test_s2", &st2);
+    
+    UCX_TEST_ASSERT(r1 == 0, "stat1 failed");
+    UCX_TEST_ASSERT(r2 == 0, "stat2 failed");
+    
+    UCX_TEST_ASSERT(st1.st_size == test_len1, "s1 wrong length");
+    UCX_TEST_ASSERT(st2.st_size == test_len2, "s2 wrong length");
+    
+    int testfail = vfs_stat(vfs, "/test_stat_fail", &st1);
+    UCX_TEST_ASSERT(testfail != 0, "stat 3 should fail");
+    
+    UCX_TEST_END;
+    
+    testutil_destroy_session(sn);
+}
+
+UCX_TEST(test_pg_vfs_mkdir) {
+    Session *sn = testutil_session();
+    Request *rq = testutil_request(sn->pool, "PUT", "/");
+    rq->vfs = create_test_pgvfs(sn, rq);
+    VFSContext *vfs = vfs_request_context(sn, rq);
+    
+    UCX_TEST_BEGIN;
+    
+    struct stat s;
+    
+    SYS_FILE f1 = vfs_open(vfs, "/test_mkdir/file", O_WRONLY|O_CREAT);
+    UCX_TEST_ASSERT(f1 == NULL, "open should fail");
+    
+    int r = vfs_mkdir(vfs, "/test_mkdir");
+    UCX_TEST_ASSERT(r == 0, "mkdir failed");
+    
+    r = vfs_stat(vfs, "/test_mkdir", &s);
+    UCX_TEST_ASSERT(r == 0, "stat (1) failed");
+    
+    UCX_TEST_ASSERT(S_ISDIR(s.st_mode), "/test_mkdir is not a directory");
+    
+    f1 = vfs_open(vfs, "/test_mkdir/file", O_WRONLY|O_CREAT);
+    vfs_close(f1);
+    UCX_TEST_ASSERT(f1, "open failed");
+    
+    r = vfs_stat(vfs, "/test_mkdir/file", &s);
+    UCX_TEST_ASSERT(r == 0, "stat (2) failed");
+
+    r = vfs_mkdir(vfs, "/test_mkdir/test_sub");
+    UCX_TEST_ASSERT(r == 0, "mkdir failed (2)");
+    
+    r = vfs_stat(vfs, "/test_mkdir/test_sub", &s);
+    UCX_TEST_ASSERT(r == 0, "stat (3) failed");
+    UCX_TEST_ASSERT(S_ISDIR(s.st_mode), "/test_mkdir/test_sub is not a directory");
+    
+    r = vfs_mkdir(vfs, "/test_mkdir/test_sub/test_sub2/");
+    UCX_TEST_ASSERT(r == 0, "mkdir failed (4)");
+    
+    r = vfs_stat(vfs, "/test_mkdir/test_sub/test_sub2/", &s);
+    UCX_TEST_ASSERT(r == 0, "stat (4) failed");
+    UCX_TEST_ASSERT(S_ISDIR(s.st_mode), "/test_mkdir/test_sub/test_sub2/ is not a directory");
+    
+    UCX_TEST_END;
+    
+    testutil_destroy_session(sn);
+}
+
+UCX_TEST(test_pg_vfs_unlink) {
+    Session *sn = testutil_session();
+    Request *rq = testutil_request(sn->pool, "PUT", "/");
+    rq->vfs = create_test_pgvfs(sn, rq);
+    VFSContext *vfs = vfs_request_context(sn, rq);
+    
+    UCX_TEST_BEGIN;
+    
+    SYS_FILE f1 = vfs_open(vfs, "/test_unlink1", O_WRONLY|O_CREAT);
+    UCX_TEST_ASSERT(f1, "cannot create test file");
+    system_fwrite(f1, "test", 4);
+    
+    PgFile *pgfile = f1->data;
+    Oid oid = pgfile->oid;
+    
+    vfs_close(f1);
+    
+    int r = vfs_unlink(vfs, "/test_unlink1");
+    UCX_TEST_ASSERT(r == 0, "unlink failed");
+    
+    f1 = vfs_open(vfs, "/test_unlink1", O_RDONLY);
+    UCX_TEST_ASSERT(f1 == NULL, "test file not deleted");
+    
+    PGresult *result = PQexec(test_connection, "savepoint sp;");
+    PQclear(result);
+    int pgfd = lo_open(test_connection, oid, INV_READ);
+    UCX_TEST_ASSERT(pgfd < 0, "large object not deleted");
+    result = PQexec(test_connection, "rollback to savepoint sp;");
+    PQclear(result);
+    
+    r = vfs_unlink(vfs, "/test_unlink1");
+    UCX_TEST_ASSERT(r, "unlink should fail");
+    
+    UCX_TEST_END;
+    
+    testutil_destroy_session(sn);
+}
+
+UCX_TEST(test_pg_vfs_rmdir) {
+    Session *sn = testutil_session();
+    Request *rq = testutil_request(sn->pool, "PUT", "/");
+    rq->vfs = create_test_pgvfs(sn, rq);
+    VFSContext *vfs = vfs_request_context(sn, rq);
+    
+    PQexec(test_connection, "delete from Resource where parent_id is not null;");
+    
+    UCX_TEST_BEGIN;
+    
+    int r;
+    SYS_FILE f1;
+    
+    // prepare some dirs/files
+    r = vfs_mkdir(vfs, "/rmdir_test");
+    UCX_TEST_ASSERT(r == 0, "mkdir failed (1)");
+    r = vfs_mkdir(vfs, "/rmdir_test/subdir1");
+    UCX_TEST_ASSERT(r == 0, "mkdir failed (2)");
+    r = vfs_mkdir(vfs, "/rmdir_test/subdir2");
+    UCX_TEST_ASSERT(r == 0, "mkdir failed (3)");
+    
+    f1 = vfs_open(vfs, "/rmdir_test/subdir2/file", O_CREAT|O_WRONLY);
+    UCX_TEST_ASSERT(f1, "open failed");
+    vfs_close(f1);
+    
+    // test rmdir
+    r = vfs_rmdir(vfs, "/rmdir_test/subdir1");
+    UCX_TEST_ASSERT(r == 0, "rmdir failed");;
+    
+    r = vfs_rmdir(vfs, "/rmdir_test/subdir2");
+    UCX_TEST_ASSERT(r != 0, "rmdir should fail if the dir is not empty");
+    
+    r = vfs_unlink(vfs, "/rmdir_test/subdir2/file");
+    UCX_TEST_ASSERT(r == 0, "unlink failed");
+    
+    r = vfs_rmdir(vfs, "/rmdir_test/subdir2");
+    UCX_TEST_ASSERT(r == 0, "rmdir failed 2");
+    
+    UCX_TEST_END;
+    
+    testutil_destroy_session(sn);
+}
+
+/* ----------------------------- WebDAV tests ----------------------------- */
+
+
+static WebdavBackend* create_test_pgdav(Session *sn, Request *rq) {
+    return pg_webdav_create_from_resdata(sn, rq, &test_repo, &resdata);
+}
+
+UCX_TEST(test_pg_webdav_create_from_resdata) {
+    Session *sn = testutil_session();
+    Request *rq = testutil_request(sn->pool, "PROPFIND", "/");
+    
+    UCX_TEST_BEGIN;
+    
+    WebdavBackend *dav = create_test_pgdav(sn, rq);
+    UCX_TEST_ASSERT(dav, "cannot create pg dav backend");
+    
+    UCX_TEST_END;
+}
+
+UCX_TEST(test_pg_prepare_tests) {
+    Session *sn = testutil_session();
+    Request *rq = testutil_request(sn->pool, "PUT", "/");
+    rq->vfs = create_test_pgvfs(sn, rq);
+    VFSContext *vfs = vfs_request_context(sn, rq);
+    
+    UCX_TEST_BEGIN;
+    
+    vfs_mkdir(vfs, "/propfind");
+    vfs_mkdir(vfs, "/proppatch");
+    SYS_FILE f1;
+    
+    int64_t res1_id, res2_id;
+    
+    f1 = vfs_open(vfs, "/propfind/res1", O_WRONLY|O_CREAT);
+    UCX_TEST_ASSERT(f1, "res1 create failed");
+    res1_id = ((PgFile*)f1->data)->resource_id;
+    vfs_close(f1);
+    
+    f1 = vfs_open(vfs, "/propfind/res2", O_WRONLY|O_CREAT);
+    UCX_TEST_ASSERT(f1, "res2 create failed");
+    res2_id = ((PgFile*)f1->data)->resource_id;
+    vfs_close(f1);
+    
+    f1 = vfs_open(vfs, "/propfind/res3", O_WRONLY|O_CREAT);
+    UCX_TEST_ASSERT(f1, "res3 create failed");
+    vfs_close(f1);
+    
+    int r = vfs_mkdir(vfs, "/propfind/sub");
+    UCX_TEST_ASSERT(r == 0, "sub create failed");
+    
+    f1 = vfs_open(vfs, "/propfind/sub/res4", O_WRONLY|O_CREAT);
+    UCX_TEST_ASSERT(f1, "res4 create failed");
+    vfs_close(f1);
+    
+    f1 = vfs_open(vfs, "/proppatch/pp1", O_WRONLY|O_CREAT);
+    UCX_TEST_ASSERT(f1, "pp1 create failed");
+    vfs_close(f1);
+    
+    // 2 properties for res1
+    char idstr[32];
+    snprintf(idstr, 32, "%" PRId64, res1_id);
+    const char* params[1] = { idstr };
+    PGresult *result = PQexecParams(
+            test_connection,
+            "insert into Property(resource_id, prefix, xmlns, pname, pvalue) values ($1, 'x', 'http://example.com/', 'test', 'testvalue');",
+            1,     // number of parameters
+            NULL,
+            params, // parameter value
+            NULL,
+            NULL,
+            0);    // 0: result in text format
+    
+    UCX_TEST_ASSERT(PQresultStatus(result) == PGRES_COMMAND_OK, "cannot create property 1");
+    PQclear(result);
+    
+    result = PQexecParams(
+            test_connection,
+            "insert into Property(resource_id, prefix, xmlns, pname, pvalue) values ($1, 'x', 'http://example.com/', 'prop2', 'value2');",
+            1,     // number of parameters
+            NULL,
+            params, // parameter value
+            NULL,
+            NULL,
+            0);    // 0: result in text format
+    
+    UCX_TEST_ASSERT(PQresultStatus(result) == PGRES_COMMAND_OK, "cannot create property 1");
+    PQclear(result);
+    
+    // 1 property for res2
+    snprintf(idstr, 32, "%" PRId64, res2_id);
+    result = PQexecParams(
+            test_connection,
+            "insert into Property(resource_id, prefix, xmlns, pname, pvalue) values ($1, 'x', 'http://example.com/', 'test', 'res2test');",
+            1,     // number of parameters
+            NULL,
+            params, // parameter value
+            NULL,
+            NULL,
+            0);    // 0: result in text format
+    
+    UCX_TEST_ASSERT(PQresultStatus(result) == PGRES_COMMAND_OK, "cannot create property 1");
+    PQclear(result);
+    
+    UCX_TEST_END;
+    
+    testutil_destroy_session(sn);
+}
+
+UCX_TEST(test_pg_webdav_propfind) {
+    Session *sn;
+    Request *rq; 
+    TestIOStream *st;
+    pblock *pb;
+    
+    UCX_TEST_BEGIN;
+    
+    // test data:
+    //
+    // /propfind/
+    // /propfind/res1     (2 properties: test, prop2)
+    // /propfind/res2     (1 property: test)
+    // /propfind/res3
+    // /propfind/sub
+    // /propfind/sub/res4
+    
+    int ret;
+    // Test 1
+    init_test_webdav_method(&sn, &rq, &st, &pb, "PROPFIND", "/propfind/", PG_TEST_PROPFIND1);
+    rq->davCollection = create_test_pgdav(sn, rq);
+    pblock_nvinsert("depth", "0", rq->headers);
+    
+    ret = webdav_propfind(pb, sn, rq);
+    
+    UCX_TEST_ASSERT(ret == REQ_PROCEED, "webdav_propfind (1) failed");
+    
+    TestMultistatus *ms = test_parse_multistatus(st->buf->space, st->buf->size);
+    UCX_TEST_ASSERT(ms, "propfind1: response is not valid xml");
+    
+    TestResponse *r1 = ucx_map_cstr_get(ms->responses, "/propfind/");
+    UCX_TEST_ASSERT(r1, "propfind1: missing /propfind/ response");
+    
+    UCX_TEST_ASSERT(ms->responses->count == 1, "propfind1: wrong response count");
+    
+    TestProperty *p = ucx_map_cstr_get(r1->properties, "DAV:resourcetype");
+    UCX_TEST_ASSERT(p, "propfind1: missing property 'resourcetype'");
+    UCX_TEST_ASSERT(p->status == 200, "propfind1: wrong status code for property 'resourcetype'");
+    
+    p = ucx_map_cstr_get(r1->properties, "DAV:getlastmodified");
+    UCX_TEST_ASSERT(p, "propfind1: missing property 'getlastmodified'");
+    UCX_TEST_ASSERT(p->status == 200, "propfind1: wrong status code for property 'getlastmodified'");
+    
+    testutil_destroy_session(sn);
+    test_multistatus_destroy(ms);
+    testutil_iostream_destroy(st);
+    
+    
+    // Test 2
+    init_test_webdav_method(&sn, &rq, &st, &pb, "PROPFIND", "/propfind/", PG_TEST_PROPFIND2);
+    rq->davCollection = create_test_pgdav(sn, rq);
+    pblock_nvinsert("depth", "1", rq->headers);
+    
+    ret = webdav_propfind(pb, sn, rq);
+    
+    //printf("\n\n%.*s\n", (int)st->buf->size, st->buf->space);
+    
+    UCX_TEST_ASSERT(ret == REQ_PROCEED, "webdav_propfind (2) failed");
+    
+    ms = test_parse_multistatus(st->buf->space, st->buf->size);
+    UCX_TEST_ASSERT(ms, "propfind2: response is not valid xml");
+    
+    r1 = ucx_map_cstr_get(ms->responses, "/propfind/");
+    UCX_TEST_ASSERT(r1, "propfind2: missing /propfind/ response");
+    
+    UCX_TEST_ASSERT(ms->responses->count == 5, "propfind2: wrong response count");
+    
+    r1 = ucx_map_cstr_get(ms->responses, "/propfind/res2");
+    UCX_TEST_ASSERT(r1, "propfind2: missing /propfind/res2 response");
+    
+    p = ucx_map_cstr_get(r1->properties, "http://example.com/test");
+    UCX_TEST_ASSERT(p, "propfind2: missing property 'test'");
+    UCX_TEST_ASSERT(p->status == 200, "propfind2: wrong status code for property 'test'");
+    UCX_TEST_ASSERT(!strcmp(p->value, "res2test"), "propfind2: wrong property value");
+    
+    
+    testutil_destroy_session(sn);
+    test_multistatus_destroy(ms);
+    testutil_iostream_destroy(st);
+    
+    
+    
+    // Test 3
+    init_test_webdav_method(&sn, &rq, &st, &pb, "PROPFIND", "/propfind/", PG_TEST_PROPFIND2);
+    rq->davCollection = create_test_pgdav(sn, rq);
+    pblock_nvinsert("depth", "infinity", rq->headers);
+    
+    ret = webdav_propfind(pb, sn, rq);
+    
+    //printf("\n\n%.*s\n", (int)st->buf->size, st->buf->space);
+    
+    UCX_TEST_ASSERT(ret == REQ_PROCEED, "webdav_propfind (3) failed");
+    
+    ms = test_parse_multistatus(st->buf->space, st->buf->size);
+    UCX_TEST_ASSERT(ms, "propfind3: response is not valid xml");
+    
+    r1 = ucx_map_cstr_get(ms->responses, "/propfind/");
+    UCX_TEST_ASSERT(r1, "propfind3: missing /propfind/ response");
+    
+    UCX_TEST_ASSERT(ms->responses->count == 6, "propfind3: wrong response count");
+    
+    
+    r1 = ucx_map_cstr_get(ms->responses, "/propfind/res1");
+    UCX_TEST_ASSERT(r1, "propfind3: missing /propfind/sub/res1 response");
+    
+    p = ucx_map_cstr_get(r1->properties, "http://example.com/test");
+    UCX_TEST_ASSERT(p, "propfind3: missing property 'test'");
+    UCX_TEST_ASSERT(p->status == 200, "propfind3: wrong status code for property 'test'");
+    UCX_TEST_ASSERT(!strcmp(p->value, "testvalue"), "propfind3: wrong property value");
+    
+    p = ucx_map_cstr_get(r1->properties, "http://example.com/prop2");
+    UCX_TEST_ASSERT(p, "propfind3: missing property 'prop2'");
+    UCX_TEST_ASSERT(p->status == 200, "propfind3: wrong status code for property 'prop2'");
+    UCX_TEST_ASSERT(!strcmp(p->value, "value2"), "propfind3: wrong property value");
+    
+    
+    r1 = ucx_map_cstr_get(ms->responses, "/propfind/sub/res4");
+    UCX_TEST_ASSERT(r1, "propfind3: missing /propfind/sub/res4 response");
+    
+    testutil_destroy_session(sn);
+    test_multistatus_destroy(ms);
+    testutil_iostream_destroy(st);
+    
+    UCX_TEST_END;
+}
+
+
+UCX_TEST(test_pg_webdav_propfind_allprop) {
+    Session *sn;
+    Request *rq; 
+    TestIOStream *st;
+    pblock *pb;
+    
+    UCX_TEST_BEGIN;
+    
+    // test data:
+    //
+    // /propfind/
+    // /propfind/res1     (2 properties: test, prop2)
+    // /propfind/res2     (1 property: test)
+    // /propfind/res3
+    // /propfind/sub
+    // /propfind/sub/res4
+    
+    int ret;
+    TestResponse *r1;
+    TestProperty *p;
+    // Test 1
+    init_test_webdav_method(&sn, &rq, &st, &pb, "PROPFIND", "/propfind/", PG_TEST_ALLPROP);
+    rq->davCollection = create_test_pgdav(sn, rq);
+    pblock_nvinsert("depth", "0", rq->headers);
+    
+    ret = webdav_propfind(pb, sn, rq);
+    
+    UCX_TEST_ASSERT(ret == REQ_PROCEED, "webdav_propfind (1) failed");
+    
+    TestMultistatus *ms = test_parse_multistatus(st->buf->space, st->buf->size);
+    UCX_TEST_ASSERT(ms, "propfind1: response is not valid xml");
+    
+    r1 = ucx_map_cstr_get(ms->responses, "/propfind/");
+    UCX_TEST_ASSERT(r1, "propfind1: missing /propfind/ response");
+    UCX_TEST_ASSERT(ms->responses->count == 1, "propfind1: wrong response count");
+    
+    p = ucx_map_cstr_get(r1->properties, "DAV:resourcetype");
+    UCX_TEST_ASSERT(r1, "propfind1: missing resourcetype property");
+    
+    testutil_destroy_session(sn);
+    test_multistatus_destroy(ms);
+    testutil_iostream_destroy(st);
+    
+    // Test 2
+    init_test_webdav_method(&sn, &rq, &st, &pb, "PROPFIND", "/propfind/", PG_TEST_ALLPROP);
+    rq->davCollection = create_test_pgdav(sn, rq);
+    pblock_nvinsert("depth", "1", rq->headers);
+    
+    ret = webdav_propfind(pb, sn, rq);
+    
+    //printf("\n\n%.*s\n", (int)st->buf->size, st->buf->space);
+    
+    UCX_TEST_ASSERT(ret == REQ_PROCEED, "webdav_propfind (2) failed");
+    
+    ms = test_parse_multistatus(st->buf->space, st->buf->size);
+    UCX_TEST_ASSERT(ms, "propfind2: response is not valid xml");
+    
+    r1 = ucx_map_cstr_get(ms->responses, "/propfind/");
+    UCX_TEST_ASSERT(r1, "propfind2: missing /propfind/ response");
+    UCX_TEST_ASSERT(ms->responses->count == 5, "propfind2: wrong response count");
+    
+    r1 = ucx_map_cstr_get(ms->responses, "/propfind/res1");
+    UCX_TEST_ASSERT(r1, "propfind2: missing /propfind/res1 response");
+    
+    p = ucx_map_cstr_get(r1->properties, "DAV:resourcetype");
+    UCX_TEST_ASSERT(r1, "propfind2: missing resourcetype property");
+    p = ucx_map_cstr_get(r1->properties, "http://example.com/test");
+    UCX_TEST_ASSERT(r1, "propfind2: missing test property");
+    p = ucx_map_cstr_get(r1->properties, "http://example.com/prop2");
+    UCX_TEST_ASSERT(r1, "propfind2: missing prop2 property");
+    
+    UCX_TEST_ASSERT(ucx_map_cstr_get(ms->responses, "/propfind/res2"), "propfind2: missing /propfind/res2 response");
+    UCX_TEST_ASSERT(ucx_map_cstr_get(ms->responses, "/propfind/res3"), "propfind2: missing /propfind/res3 response");
+    UCX_TEST_ASSERT(ucx_map_cstr_get(ms->responses, "/propfind/sub/"), "propfind2: missing /propfind/sub response");
+    
+    testutil_destroy_session(sn);
+    test_multistatus_destroy(ms);
+    testutil_iostream_destroy(st);
+    
+    // Test 3
+    init_test_webdav_method(&sn, &rq, &st, &pb, "PROPFIND", "/propfind/", PG_TEST_ALLPROP);
+    rq->davCollection = create_test_pgdav(sn, rq);
+    pblock_nvinsert("depth", "infinity", rq->headers);
+    
+    ret = webdav_propfind(pb, sn, rq);
+    
+    UCX_TEST_ASSERT(ret == REQ_PROCEED, "webdav_propfind (2) failed");
+    
+    ms = test_parse_multistatus(st->buf->space, st->buf->size);
+    UCX_TEST_ASSERT(ms, "propfind3: response is not valid xml");
+    
+    r1 = ucx_map_cstr_get(ms->responses, "/propfind/");
+    UCX_TEST_ASSERT(r1, "propfind3: missing /propfind/ response");
+    UCX_TEST_ASSERT(ms->responses->count == 6, "propfind3: wrong response count");
+    
+    r1 = ucx_map_cstr_get(ms->responses, "/propfind/res1");
+    UCX_TEST_ASSERT(r1, "propfind3: missing /propfind/res1 response");
+    
+    p = ucx_map_cstr_get(r1->properties, "DAV:resourcetype");
+    UCX_TEST_ASSERT(r1, "propfind3: missing resourcetype property");
+    p = ucx_map_cstr_get(r1->properties, "http://example.com/test");
+    UCX_TEST_ASSERT(r1, "propfind3: missing test property");
+    p = ucx_map_cstr_get(r1->properties, "http://example.com/prop2");
+    UCX_TEST_ASSERT(r1, "propfind3: missing prop2 property");
+    
+    UCX_TEST_ASSERT(ucx_map_cstr_get(ms->responses, "/propfind/res2"), "propfind3: missing /propfind/res2 response");
+    UCX_TEST_ASSERT(ucx_map_cstr_get(ms->responses, "/propfind/res3"), "propfind3: missing /propfind/res3 response");
+    UCX_TEST_ASSERT(ucx_map_cstr_get(ms->responses, "/propfind/sub/"), "propfind3: missing /propfind/sub response");
+    UCX_TEST_ASSERT(ucx_map_cstr_get(ms->responses, "/propfind/sub/res4"), "propfind3: missing /propfind/sub/res4 response");
+    
+    testutil_destroy_session(sn);
+    test_multistatus_destroy(ms);
+    testutil_iostream_destroy(st);
+    
+    
+    UCX_TEST_END;
+}
+
+UCX_TEST(test_pg_webdav_proppatch_set) {
+    Session *sn;
+    Request *rq; 
+    TestIOStream *st;
+    pblock *pb;
+    
+    UCX_TEST_BEGIN;
+    
+    // test data:
+    //
+    // /propfind/
+    // /propfind/res1     (2 properties: test, prop2)
+    // /propfind/res2     (1 property: test)
+    // /propfind/res3
+    // /propfind/sub
+    // /propfind/sub/res4
+    
+    int ret;
+    TestResponse *r1;
+    TestProperty *p;
+    // Test 1
+    init_test_webdav_method(&sn, &rq, &st, &pb, "PROPPATCH", "/proppatch/pp1", PG_TEST_PROPPATCH1);
+    rq->davCollection = create_test_pgdav(sn, rq);
+    
+    ret = webdav_proppatch(pb, sn, rq);
+    UCX_TEST_ASSERT(ret == REQ_PROCEED, "proppatch1 failed");
+    
+    //printf("\n\n%.*s\n", (int)st->buf->size, st->buf->space);
+    
+    TestMultistatus *ms = test_parse_multistatus(st->buf->space, st->buf->size);
+    UCX_TEST_ASSERT(ms, "proppatch1 response is not valid xml");
+    
+    testutil_destroy_session(sn);
+    test_multistatus_destroy(ms);
+    testutil_iostream_destroy(st);
+    
+    // Test 2: xml property value
+    init_test_webdav_method(&sn, &rq, &st, &pb, "PROPPATCH", "/proppatch/pp1", PG_TEST_PROPPATCH2);
+    rq->davCollection = create_test_pgdav(sn, rq);
+    
+    ret = webdav_proppatch(pb, sn, rq);
+    UCX_TEST_ASSERT(ret == REQ_PROCEED, "proppatch2 failed");
+    
+    //printf("\n\n%.*s\n", (int)st->buf->size, st->buf->space);
+    
+    ms = test_parse_multistatus(st->buf->space, st->buf->size);
+    UCX_TEST_ASSERT(ms, "proppatch2 response is not valid xml");
+    
+    testutil_destroy_session(sn);
+    test_multistatus_destroy(ms);
+    testutil_iostream_destroy(st);
+    
+    
+    UCX_TEST_END;
+}

mercurial