--- /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; +}