#if HAVE_CONFIG_H
#include <config.h>
#endif
#include <assert.h>
#include "cunit/cyrunit.h"
#include "lib/libconfig.h"
#include "lib/libcyr_cfg.h"
#include "lib/xunlink.h"
#include "imap/mboxname.h"
#include "imap/mailbox.h"
#include "imap/global.h"

#define DBDIR           "test-dbdir"

static void test_dir_hash_c(void)
{
    static const char FRED[] = "fred";
    static const char FRED_DRAFTS[] = "fred.Drafts";
    static const char JANEAT_SENT[] = "bloggs.com!user.jane.Sent";
    static const char SHARED[] = "Gossip";
    static const char INTERNAL[] = "internal!Tattle";
    static const char LONGNAME[] = "thisisareallylongname";
    static const char LONGNAME_SUB[] = "thisisareallylongname.with.a.subdir";
    char c;

    c = dir_hash_c(FRED, 0);
    CU_ASSERT_EQUAL(c, 'f');
    c = dir_hash_c(FRED_DRAFTS, 0);
    CU_ASSERT_EQUAL(c, 'f');
    c = dir_hash_c(JANEAT_SENT, 0);
    CU_ASSERT_EQUAL(c, 'b');
    c = dir_hash_c(SHARED, 0);
    CU_ASSERT_EQUAL(c, 'g');
    c = dir_hash_c(INTERNAL, 0);
    CU_ASSERT_EQUAL(c, 'i');
    c = dir_hash_c(LONGNAME, 0);
    CU_ASSERT_EQUAL(c, 't');
    c = dir_hash_c(LONGNAME_SUB, 0);
    CU_ASSERT_EQUAL(c, 't');

    c = dir_hash_c(FRED, 1);
    CU_ASSERT_EQUAL(c, 'E');
    c = dir_hash_c(FRED_DRAFTS, 1);
    CU_ASSERT_EQUAL(c, 'E');
    c = dir_hash_c(JANEAT_SENT, 1);
    CU_ASSERT_EQUAL(c, 'L');
    c = dir_hash_c(SHARED, 1);
    CU_ASSERT_EQUAL(c, 'K');
    c = dir_hash_c(INTERNAL, 1);
    CU_ASSERT_EQUAL(c, 'I');
    c = dir_hash_c(LONGNAME, 1);
    CU_ASSERT_EQUAL(c, 'J');
    c = dir_hash_c(LONGNAME_SUB, 1);
    CU_ASSERT_EQUAL(c, 'J');
}

static void test_mboxname_hash(void)
{
    static const char FRED[] = "fred";
    static const char JANEAT_SENT[] = "bloggs.com!user.jane.Sent";
    char s[MAX_MAILBOX_PATH];

    mboxname_hash(s, MAX_MAILBOX_PATH, "/tmp", FRED);
    CU_ASSERT_STRING_EQUAL(s, "/tmp/fred");

    mboxname_hash(s, MAX_MAILBOX_PATH, "/tmp", JANEAT_SENT);
    CU_ASSERT_STRING_EQUAL(s, "/tmp/domain/bloggs.com/user/jane/Sent");

    config_hashimapspool = 1;

    mboxname_hash(s, MAX_MAILBOX_PATH, "/tmp", FRED);
    CU_ASSERT_STRING_EQUAL(s, "/tmp/f/fred");

    mboxname_hash(s, MAX_MAILBOX_PATH, "/tmp", JANEAT_SENT);
    CU_ASSERT_STRING_EQUAL(s, "/tmp/domain/b/bloggs.com/j/user/jane/Sent");

    config_fulldirhash = 1;

    mboxname_hash(s, MAX_MAILBOX_PATH, "/tmp", FRED);
    CU_ASSERT_STRING_EQUAL(s, "/tmp/E/fred");

    mboxname_hash(s, MAX_MAILBOX_PATH, "/tmp", JANEAT_SENT);
    CU_ASSERT_STRING_EQUAL(s, "/tmp/domain/L/bloggs.com/I/user/jane/Sent");
}

static void test_to_parts(void)
{
    static const char FRED_DRAFTS[] = "user.fred.Drafts";
    static const char JANEAT_SENT[] = "bloggs.com!user.jane.Sent";
    static const char SHARED[] = "shared.Gossip";
    static const char SHAREDAT[] = "foonly.com!shared.Tattle";
    static const char A_DEEP_FOLDER[] = "user.fred.a.deep.folder";
    mbname_t *mbname = NULL;
    const strarray_t *boxes;

    mbname = mbname_from_intname(FRED_DRAFTS);
    CU_ASSERT_PTR_NULL(mbname_domain(mbname));
    CU_ASSERT_STRING_EQUAL(mbname_localpart(mbname), "fred");
    CU_ASSERT_STRING_EQUAL(mbname_userid(mbname), "fred");
    boxes = mbname_boxes(mbname);
    CU_ASSERT_EQUAL(boxes->count, 1);
    CU_ASSERT_STRING_EQUAL(boxes->data[0], "Drafts");
    mbname_free(&mbname);

    mbname = mbname_from_intname(JANEAT_SENT);
    CU_ASSERT_STRING_EQUAL(mbname_domain(mbname), "bloggs.com");
    CU_ASSERT_STRING_EQUAL(mbname_localpart(mbname), "jane");
    CU_ASSERT_STRING_EQUAL(mbname_userid(mbname), "jane@bloggs.com");
    boxes = mbname_boxes(mbname);
    CU_ASSERT_EQUAL(boxes->count, 1);
    CU_ASSERT_STRING_EQUAL(boxes->data[0], "Sent");
    mbname_free(&mbname);

    mbname = mbname_from_intname(SHARED);
    CU_ASSERT_PTR_NULL(mbname_domain(mbname));
    CU_ASSERT_PTR_NULL(mbname_localpart(mbname));
    CU_ASSERT_PTR_NULL(mbname_userid(mbname));
    boxes = mbname_boxes(mbname);
    CU_ASSERT_EQUAL(boxes->count, 2);
    CU_ASSERT_STRING_EQUAL(boxes->data[0], "shared");
    CU_ASSERT_STRING_EQUAL(boxes->data[1], "Gossip");
    mbname_free(&mbname);

    mbname = mbname_from_intname(SHAREDAT);
    CU_ASSERT_STRING_EQUAL(mbname_domain(mbname), "foonly.com");
    CU_ASSERT_PTR_NULL(mbname_localpart(mbname));
    CU_ASSERT_PTR_NULL(mbname_userid(mbname));
    boxes = mbname_boxes(mbname);
    CU_ASSERT_EQUAL(boxes->count, 2);
    CU_ASSERT_STRING_EQUAL(boxes->data[0], "shared");
    CU_ASSERT_STRING_EQUAL(boxes->data[1], "Tattle");
    mbname_free(&mbname);

    mbname = mbname_from_intname(A_DEEP_FOLDER);
    CU_ASSERT_PTR_NULL(mbname_domain(mbname));
    CU_ASSERT_STRING_EQUAL(mbname_localpart(mbname), "fred");
    CU_ASSERT_STRING_EQUAL(mbname_userid(mbname), "fred");
    boxes = mbname_boxes(mbname);
    CU_ASSERT_EQUAL(boxes->count, 3);
    CU_ASSERT_STRING_EQUAL(boxes->data[0], "a");
    CU_ASSERT_STRING_EQUAL(boxes->data[1], "deep");
    CU_ASSERT_STRING_EQUAL(boxes->data[2], "folder");
    mbname_free(&mbname);
}

static void test_to_userid(void)
{
    static const char SAM_DRAFTS[] = "user.sam.Drafts";
    static const char BETTYAT_SENT[] = "boop.com!user.betty.Sent";
    static const char SHARED[] = "shared.Gossip";
    static const char SHAREDAT[] = "foonly.com!shared.Tattle";
    char *r;

    r = mboxname_to_userid(SAM_DRAFTS);
    CU_ASSERT_STRING_EQUAL(r, "sam");
    free(r);

    r = mboxname_to_userid(BETTYAT_SENT);
    CU_ASSERT_STRING_EQUAL(r, "betty@boop.com");
    free(r);

    r = mboxname_to_userid(SHARED);
    CU_ASSERT_PTR_NULL(r);
    free(r);

    r = mboxname_to_userid(SHAREDAT);
    CU_ASSERT_PTR_NULL(r);
    free(r);
}

static void test_to_usermbox(void)
{
    char *r;

    r = mboxname_user_mbox(NULL, NULL);
    CU_ASSERT_PTR_NULL(r);

    r = mboxname_user_mbox(NULL, "extra.stuff");
    CU_ASSERT_PTR_NULL(r);

    r = mboxname_user_mbox("sam", NULL);
    CU_ASSERT_STRING_EQUAL(r, "user.sam");
    free(r);

    r = mboxname_user_mbox("betty@boop.com", NULL);
    CU_ASSERT_STRING_EQUAL(r, "boop.com!user.betty");
    free(r);

    r = mboxname_user_mbox("sam", "sub");
    CU_ASSERT_STRING_EQUAL(r, "user.sam.sub");
    free(r);

    r = mboxname_user_mbox("sam", "sub.deep.stuff");
    CU_ASSERT_STRING_EQUAL(r, "user.sam.sub.deep.stuff");
    free(r);

    r = mboxname_user_mbox("betty@boop.com", "sub");
    CU_ASSERT_STRING_EQUAL(r, "boop.com!user.betty.sub");
    free(r);

    r = mboxname_user_mbox("betty@boop.com", "sub.deep.stuff");
    CU_ASSERT_STRING_EQUAL(r, "boop.com!user.betty.sub.deep.stuff");
    free(r);
}


static void test_same_userid(void)
{
    static const char FRED_DRAFTS[] = "user.fred.Drafts";
    static const char FRED_SENT[] = "user.fred.Sent";
    static const char JANE_SENT[] = "user.jane.Sent";

    CU_ASSERT_EQUAL(mboxname_same_userid(FRED_DRAFTS, FRED_SENT), 1);
    CU_ASSERT_EQUAL(mboxname_same_userid(JANE_SENT, FRED_SENT), 0);
}

static void test_same_userid_domain(void)
{
    static const char FREDAT_DRAFTS[] = "bloggs.com!user.fred.Drafts";
    static const char FREDAT_SENT[] = "bloggs.com!user.fred.Sent";
    static const char JANEAT_SENT[] = "bloggs.com!user.jane.Sent";
    static const char JANE_SENT[] = "user.jane.Sent";

    CU_ASSERT_EQUAL(mboxname_same_userid(FREDAT_DRAFTS, FREDAT_SENT), 1);
    CU_ASSERT_EQUAL(mboxname_same_userid(JANEAT_SENT, FREDAT_SENT), 0);
    CU_ASSERT_EQUAL(mboxname_same_userid(JANE_SENT, FREDAT_SENT), 0);
    CU_ASSERT_EQUAL(mboxname_same_userid(JANE_SENT, JANEAT_SENT), 0);
}

static void test_contains(void)
{
    static const char FOO[] = "bloggs.com!user.foo";
    static const char FOOBAR[] = "bloggs.com!user.foobar";
    static const char FOODRAFT[] = "bloggs.com!user.foo.Drafts";
    static const char FOONET[] = "bloggs.net!user.foo";
    static const char FOONONE[] = "user.foo";

    CU_ASSERT_EQUAL(mboxname_is_prefix(FOO, FOOBAR), 0);
    CU_ASSERT_EQUAL(mboxname_is_prefix(FOOBAR, FOO), 0);
    CU_ASSERT_EQUAL(mboxname_is_prefix(FOO, FOODRAFT), 0);
    CU_ASSERT_EQUAL(mboxname_is_prefix(FOODRAFT, FOO), 1);
    CU_ASSERT_EQUAL(mboxname_is_prefix(FOOBAR, FOOBAR), 1);
    CU_ASSERT_EQUAL(mboxname_is_prefix(FOO, FOONET), 0);
    CU_ASSERT_EQUAL(mboxname_is_prefix(FOONET, FOO), 0);
    CU_ASSERT_EQUAL(mboxname_is_prefix(FOONONE, FOO), 0);
    CU_ASSERT_EQUAL(mboxname_is_prefix(FOO, FOONONE), 0);
}

static void test_parts_same_userid(void)
{
    static const char FRED_DRAFTS[] = "user.fred.Drafts";
    static const char FRED_SENT[] = "user.fred.Sent";
    static const char JANE_SENT[] = "user.jane.Sent";
    mbname_t *n1 = NULL, *n2 = NULL;

    n1 = mbname_from_intname(FRED_DRAFTS);
    CU_ASSERT_PTR_NOT_NULL(n1);
    n2 = mbname_from_intname(FRED_SENT);
    CU_ASSERT_PTR_NOT_NULL(n2);
    CU_ASSERT_EQUAL(mbname_same_userid(n1, n2), 1);
    mbname_free(&n1);
    mbname_free(&n2);

    n1 = mbname_from_intname(JANE_SENT);
    CU_ASSERT_PTR_NOT_NULL(n1);
    n2 = mbname_from_intname(FRED_SENT);
    CU_ASSERT_PTR_NOT_NULL(n2);
    CU_ASSERT_EQUAL(mbname_same_userid(n1, n2), 0);
    mbname_free(&n1);
    mbname_free(&n2);
}

static void test_parts_same_userid_domain(void)
{
    static const char FREDAT_DRAFTS[] = "bloggs.com!user.fred.Drafts";
    static const char FREDAT_SENT[] = "bloggs.com!user.fred.Sent";
    static const char JANEAT_SENT[] = "bloggs.com!user.jane.Sent";
    static const char JANE_SENT[] = "user.jane.Sent";
    mbname_t *n1 = NULL, *n2 = NULL;

    n1 = mbname_from_intname(FREDAT_DRAFTS);
    CU_ASSERT_PTR_NOT_NULL(n1);
    n2 = mbname_from_intname(FREDAT_SENT);
    CU_ASSERT_PTR_NOT_NULL(n2);
    CU_ASSERT_EQUAL(mbname_same_userid(n1, n2), 1);
    mbname_free(&n1);
    mbname_free(&n2);

    n1 = mbname_from_intname(JANEAT_SENT);
    CU_ASSERT_PTR_NOT_NULL(n1);
    n2 = mbname_from_intname(FREDAT_SENT);
    CU_ASSERT_PTR_NOT_NULL(n2);
    CU_ASSERT_EQUAL(mbname_same_userid(n1, n2), 0);
    mbname_free(&n1);
    mbname_free(&n2);

    n1 = mbname_from_intname(JANE_SENT);
    CU_ASSERT_PTR_NOT_NULL(n1);
    n2 = mbname_from_intname(FREDAT_SENT);
    CU_ASSERT_PTR_NOT_NULL(n2);
    CU_ASSERT_EQUAL(mbname_same_userid(n1, n2), 0);
    mbname_free(&n1);
    mbname_free(&n2);

    n1 = mbname_from_intname(JANE_SENT);
    CU_ASSERT_PTR_NOT_NULL(n1);
    n2 = mbname_from_intname(JANEAT_SENT);
    CU_ASSERT_PTR_NOT_NULL(n2);
    CU_ASSERT_EQUAL(mbname_same_userid(n1, n2), 0);
    mbname_free(&n1);
    mbname_free(&n2);
}

/* This structure encapsulates all the variables which affect
 * namespace translation in one place */
static struct
{
    const char *userid;
    int isadmin;
    int altnamespace;
    int virtdomains;
    int crossdomains;
    int cdother;
    const char *defdomain;
    const char *userprefix;
    const char *sharedprefix;
    int unixhierarchysep;
} conf;

#define toexternal_helper(i, e) do {                                      \
    const char *intname = (i);                                            \
    const char *extname_expected = (e);                                   \
    struct namespace ns;                                                  \
    int r;                                                                \
                                                                          \
    config_virtdomains = conf.virtdomains;                                \
    config_defdomain = conf.defdomain;                                    \
    imapopts[IMAPOPT_UNIXHIERARCHYSEP].val.b = conf.unixhierarchysep;     \
    imapopts[IMAPOPT_CROSSDOMAINS].val.b = conf.crossdomains;             \
    imapopts[IMAPOPT_CROSSDOMAINS_ONLYOTHER].val.b = conf.cdother;        \
    imapopts[IMAPOPT_ALTNAMESPACE].val.b = conf.altnamespace;             \
    imapopts[IMAPOPT_USERPREFIX].val.s = conf.userprefix;                 \
    imapopts[IMAPOPT_SHAREDPREFIX].val.s = conf.sharedprefix;             \
                                                                          \
    r = mboxname_init_namespace(&ns, conf.isadmin);                       \
    CU_ASSERT_EQUAL_FATAL(r, 0);                                          \
                                                                          \
    if (intname) {                                                        \
        char *extname = mboxname_to_external(intname, &ns, conf.userid);  \
        CU_ASSERT_STRING_EQUAL(extname, extname_expected);                \
        free(extname);                                                    \
    }                                                                     \
                                                                          \
    if (extname_expected) {                                               \
        char *intname_reversed = mboxname_from_external(extname_expected, \
                                                        &ns, conf.userid);\
        CU_ASSERT_STRING_EQUAL(intname, intname_reversed);                \
        free(intname_reversed);                                           \
    }                                                                     \
} while(0)

static void test_toexternal_admin(void)
{
    memset(&conf, 0, sizeof(conf));
    conf.virtdomains = 0;
    conf.userid = "admin";
    conf.isadmin = 1;

    toexternal_helper("user.fred", "user.fred");
    toexternal_helper("user.fred.foo", "user.fred.foo");
    toexternal_helper("user.fred.foo.barracuda", "user.fred.foo.barracuda");
    toexternal_helper("shared.quux", "shared.quux");
    toexternal_helper("DELETED.user.foo.55E39AD4", "DELETED.user.foo.55E39AD4");
    toexternal_helper("DELETED.user.foo.sub.55E39AD4", "DELETED.user.foo.sub.55E39AD4");
    toexternal_helper("DELETED.shared.quux.55E39AD4", "DELETED.shared.quux.55E39AD4");

    conf.virtdomains = 1;

    /* should still work the same */
    toexternal_helper("user.fred", "user.fred");
    toexternal_helper("user.fred.foo", "user.fred.foo");
    toexternal_helper("user.fred.foo.barracuda", "user.fred.foo.barracuda");
    toexternal_helper("shared.quux", "shared.quux");
    toexternal_helper("DELETED.user.foo.55E39AD4", "DELETED.user.foo.55E39AD4");
    toexternal_helper("DELETED.user.foo.sub.55E39AD4", "DELETED.user.foo.sub.55E39AD4");
    toexternal_helper("DELETED.shared.quux.55E39AD4", "DELETED.shared.quux.55E39AD4");

    /* domains always at the end */
    toexternal_helper("domain.com!user.fred", "user.fred@domain.com");
    toexternal_helper("domain.com!user.fred.foo", "user.fred.foo@domain.com");
    toexternal_helper("domain.com!user.fred.foo.barracuda", "user.fred.foo.barracuda@domain.com");
    toexternal_helper("domain.com!shared.quux", "shared.quux@domain.com");
    toexternal_helper("domain.com!DELETED.user.foo.55E39AD4", "DELETED.user.foo.55E39AD4@domain.com");
    toexternal_helper("domain.com!DELETED.user.foo.sub.55E39AD4", "DELETED.user.foo.sub.55E39AD4@domain.com");
    toexternal_helper("domain.com!DELETED.shared.quux.55E39AD4", "DELETED.shared.quux.55E39AD4@domain.com");

    conf.unixhierarchysep = 1;

    /* should still work the same */
    toexternal_helper("user.fred", "user/fred");
    toexternal_helper("user.fred.foo", "user/fred/foo");
    toexternal_helper("user.fred.foo.barracuda", "user/fred/foo/barracuda");
    toexternal_helper("shared.quux", "shared/quux");
    toexternal_helper("DELETED.user.foo.55E39AD4", "DELETED/user/foo/55E39AD4");
    toexternal_helper("DELETED.user.foo.sub.55E39AD4", "DELETED/user/foo/sub/55E39AD4");
    toexternal_helper("DELETED.shared.quux.55E39AD4", "DELETED/shared/quux/55E39AD4");

    /* domains always at the end */
    toexternal_helper("domain.com!user.fred", "user/fred@domain.com");
    toexternal_helper("domain.com!user.fred.foo", "user/fred/foo@domain.com");
    toexternal_helper("domain.com!user.fred.foo.barracuda", "user/fred/foo/barracuda@domain.com");
    toexternal_helper("domain.com!shared.quux", "shared/quux@domain.com");
    toexternal_helper("domain.com!DELETED.user.foo.55E39AD4", "DELETED/user/foo/55E39AD4@domain.com");
    toexternal_helper("domain.com!DELETED.user.foo.sub.55E39AD4", "DELETED/user/foo/sub/55E39AD4@domain.com");
    toexternal_helper("domain.com!DELETED.shared.quux.55E39AD4", "DELETED/shared/quux/55E39AD4@domain.com");

    /* users with dots in them, and folders with dots */
    toexternal_helper("user.fred^bloggs", "user/fred.bloggs");
    toexternal_helper("user.fred^bloggs.sub", "user/fred.bloggs/sub");
    toexternal_helper("user.fred^bloggs.sub^folder.parts", "user/fred.bloggs/sub.folder/parts");

    toexternal_helper("domain.com!user.fred^bloggs", "user/fred.bloggs@domain.com");
    toexternal_helper("domain.com!user.fred^bloggs.sub", "user/fred.bloggs/sub@domain.com");

    conf.virtdomains = 0;

    /* should still work the same */
    toexternal_helper("user.fred", "user/fred");
    toexternal_helper("user.fred.foo", "user/fred/foo");
    toexternal_helper("user.fred.foo.barracuda", "user/fred/foo/barracuda");
    toexternal_helper("shared.quux", "shared/quux");
    toexternal_helper("DELETED.user.foo.55E39AD4", "DELETED/user/foo/55E39AD4");
    toexternal_helper("DELETED.user.foo.sub.55E39AD4", "DELETED/user/foo/sub/55E39AD4");
    toexternal_helper("DELETED.shared.quux.55E39AD4", "DELETED/shared/quux/55E39AD4");

    /* users with dots in them, and folders with dots */
    toexternal_helper("user.fred^bloggs", "user/fred.bloggs");
    toexternal_helper("user.fred^bloggs.sub", "user/fred.bloggs/sub");
    toexternal_helper("user.fred^bloggs.sub^folder.parts", "user/fred.bloggs/sub.folder/parts");

    /* mailboxes with @ in them should be OK too */
    toexternal_helper("user.fred^bloggs.with@at", "user/fred.bloggs/with@at");

    /* and users with @ in them */
    toexternal_helper("user.fred@bloggs^com.Trash", "user/fred@bloggs.com/Trash");
}

static void test_toexternal_admin_altns(void)
{
    memset(&conf, 0, sizeof(conf));
    conf.virtdomains = 0;
    conf.userid = "admin";
    conf.isadmin = 1;
    conf.altnamespace = 1; /* NOTE: admin is never in alt namespace! */

    toexternal_helper("user.fred", "user.fred");
    toexternal_helper("user.fred.foo", "user.fred.foo");
    toexternal_helper("user.fred.foo.barracuda", "user.fred.foo.barracuda");
    toexternal_helper("shared.quux", "shared.quux");
    toexternal_helper("DELETED.user.foo.55E39AD4", "DELETED.user.foo.55E39AD4");
    toexternal_helper("DELETED.user.foo.sub.55E39AD4", "DELETED.user.foo.sub.55E39AD4");
    toexternal_helper("DELETED.shared.quux.55E39AD4", "DELETED.shared.quux.55E39AD4");

    conf.virtdomains = 1;

    /* should still work the same */
    toexternal_helper("user.fred", "user.fred");
    toexternal_helper("user.fred.foo", "user.fred.foo");
    toexternal_helper("user.fred.foo.barracuda", "user.fred.foo.barracuda");
    toexternal_helper("shared.quux", "shared.quux");
    toexternal_helper("DELETED.user.foo.55E39AD4", "DELETED.user.foo.55E39AD4");
    toexternal_helper("DELETED.user.foo.sub.55E39AD4", "DELETED.user.foo.sub.55E39AD4");
    toexternal_helper("DELETED.shared.quux.55E39AD4", "DELETED.shared.quux.55E39AD4");

    /* domains always at the end */
    toexternal_helper("domain.com!user.fred", "user.fred@domain.com");
    toexternal_helper("domain.com!user.fred.foo", "user.fred.foo@domain.com");
    toexternal_helper("domain.com!user.fred.foo.barracuda", "user.fred.foo.barracuda@domain.com");
    toexternal_helper("domain.com!shared.quux", "shared.quux@domain.com");
    toexternal_helper("domain.com!DELETED.user.foo.55E39AD4", "DELETED.user.foo.55E39AD4@domain.com");
    toexternal_helper("domain.com!DELETED.user.foo.sub.55E39AD4", "DELETED.user.foo.sub.55E39AD4@domain.com");
    toexternal_helper("domain.com!DELETED.shared.quux.55E39AD4", "DELETED.shared.quux.55E39AD4@domain.com");

    conf.unixhierarchysep = 1;

    /* should still work the same */
    toexternal_helper("user.fred", "user/fred");
    toexternal_helper("user.fred.foo", "user/fred/foo");
    toexternal_helper("user.fred.foo.barracuda", "user/fred/foo/barracuda");
    toexternal_helper("shared.quux", "shared/quux");
    toexternal_helper("DELETED.user.foo.55E39AD4", "DELETED/user/foo/55E39AD4");
    toexternal_helper("DELETED.user.foo.sub.55E39AD4", "DELETED/user/foo/sub/55E39AD4");
    toexternal_helper("DELETED.shared.quux.55E39AD4", "DELETED/shared/quux/55E39AD4");

    /* domains always at the end */
    toexternal_helper("domain.com!user.fred", "user/fred@domain.com");
    toexternal_helper("domain.com!user.fred.foo", "user/fred/foo@domain.com");
    toexternal_helper("domain.com!user.fred.foo.barracuda", "user/fred/foo/barracuda@domain.com");
    toexternal_helper("domain.com!shared.quux", "shared/quux@domain.com");
    toexternal_helper("domain.com!DELETED.user.foo.55E39AD4", "DELETED/user/foo/55E39AD4@domain.com");
    toexternal_helper("domain.com!DELETED.user.foo.sub.55E39AD4", "DELETED/user/foo/sub/55E39AD4@domain.com");
    toexternal_helper("domain.com!DELETED.shared.quux.55E39AD4", "DELETED/shared/quux/55E39AD4@domain.com");

    /* users with dots in them, and folders with dots */
    toexternal_helper("user.fred^bloggs", "user/fred.bloggs");
    toexternal_helper("user.fred^bloggs.sub", "user/fred.bloggs/sub");
    toexternal_helper("user.fred^bloggs.sub^folder.parts", "user/fred.bloggs/sub.folder/parts");

    toexternal_helper("domain.com!user.fred^bloggs", "user/fred.bloggs@domain.com");
    toexternal_helper("domain.com!user.fred^bloggs.sub", "user/fred.bloggs/sub@domain.com");

    conf.virtdomains = 0;

    /* should still work the same */
    toexternal_helper("user.fred", "user/fred");
    toexternal_helper("user.fred.foo", "user/fred/foo");
    toexternal_helper("user.fred.foo.barracuda", "user/fred/foo/barracuda");
    toexternal_helper("shared.quux", "shared/quux");
    toexternal_helper("DELETED.user.foo.55E39AD4", "DELETED/user/foo/55E39AD4");
    toexternal_helper("DELETED.user.foo.sub.55E39AD4", "DELETED/user/foo/sub/55E39AD4");
    toexternal_helper("DELETED.shared.quux.55E39AD4", "DELETED/shared/quux/55E39AD4");

    /* users with dots in them, and folders with dots */
    toexternal_helper("user.fred^bloggs", "user/fred.bloggs");
    toexternal_helper("user.fred^bloggs.sub", "user/fred.bloggs/sub");
    toexternal_helper("user.fred^bloggs.sub^folder.parts", "user/fred.bloggs/sub.folder/parts");
}

static void test_toexternal_simple(void)
{
    memset(&conf, 0, sizeof(conf));
    conf.virtdomains = 0;
    conf.userid = "fred";

    toexternal_helper("user.fred", "INBOX");
    toexternal_helper("user.fred.foo", "INBOX.foo");
    toexternal_helper("user.fred.foo.barracuda", "INBOX.foo.barracuda");
    toexternal_helper("user.jane", "user.jane");
    toexternal_helper("user.jane.baz", "user.jane.baz");
    toexternal_helper("user.jane.with^a^hat", "user.jane.with^a^hat");
    toexternal_helper("user.hatty^smith", "user.hatty^smith");
    toexternal_helper("shared.quux", "shared.quux");

    /* check that @ doesn't get polluted */
    toexternal_helper("user.fred.folder@place", "INBOX.folder@place");
}

static void test_toexternal_domains(void)
{
    memset(&conf, 0, sizeof(conf));
    conf.virtdomains = 1;
    conf.userid = "fred@bloggs.com";

    toexternal_helper("bloggs.com!user.fred", "INBOX");
    toexternal_helper("bloggs.com!user.fred.foo", "INBOX.foo");
    toexternal_helper("bloggs.com!user.fred.foo.barracuda", "INBOX.foo.barracuda");
    toexternal_helper("bloggs.com!user.jane", "user.jane");
    toexternal_helper("bloggs.com!user.jane.baz", "user.jane.baz");
    toexternal_helper("bloggs.com!user.jane.with^a^hat", "user.jane.with^a^hat");
    toexternal_helper("bloggs.com!user.hatty^smith", "user.hatty^smith");
    toexternal_helper("bloggs.com!shared.quux", "shared.quux");
    /* cross domain sharing is not allowed */
    toexternal_helper("boop.com!user.betty", NULL);
    toexternal_helper("boop.com!shared.quux", NULL);
}

static void test_toexternal_crossdomains_other(void)
{
    memset(&conf, 0, sizeof(conf));
    conf.virtdomains = 1;
    conf.crossdomains = 1;
    conf.cdother = 1;
    conf.userid = "fred@bloggs.com";

    toexternal_helper("bloggs.com!user.fred", "INBOX");
    toexternal_helper("bloggs.com!user.jane", "user.jane");
    toexternal_helper("boop.com!user.betty", "user.betty@boop^com");
}

static void test_toexternal_crossdomains(void)
{
    memset(&conf, 0, sizeof(conf));
    conf.virtdomains = 1;
    conf.crossdomains = 1;
    conf.unixhierarchysep = 1;
    conf.userid = "fred@bloggs.com";

    toexternal_helper("bloggs.com!user.fred", "INBOX");
    toexternal_helper("bloggs.com!user.fred.foo", "INBOX/foo");
    toexternal_helper("bloggs.com!user.fred.foo.barracuda", "INBOX/foo/barracuda");
    toexternal_helper("bloggs.com!user.jane", "user/jane@bloggs.com");
    toexternal_helper("bloggs.com!user.jane.baz", "user/jane@bloggs.com/baz");
    toexternal_helper("bloggs.com!user.jane.with^a^hat", "user/jane@bloggs.com/with.a.hat");
    toexternal_helper("bloggs.com!user.hatty^smith", "user/hatty.smith@bloggs.com");
    // no domains in shared folders in crossdomain
    toexternal_helper("bloggs.com!shared.quux", "shared/quux");

    toexternal_helper("boop.com!user.betty", "user/betty@boop.com");
    toexternal_helper("boop.com!user.betty.foo.bar", "user/betty@boop.com/foo/bar");
    toexternal_helper("boop.com!user.betty.foo^bar", "user/betty@boop.com/foo.bar");
    // shared folders in other domains can't be seen
    toexternal_helper("boop.com!shared.quux", NULL);
}

static void test_toexternal_crossdomains_nouhs(void)
{
    memset(&conf, 0, sizeof(conf));
    conf.virtdomains = 1;
    conf.crossdomains = 1;
    conf.unixhierarchysep = 0;
    conf.userid = "fred@bloggs.com";

    toexternal_helper("bloggs.com!user.fred", "INBOX");
    toexternal_helper("bloggs.com!user.fred.foo", "INBOX.foo");
    toexternal_helper("bloggs.com!user.fred.foo.barracuda", "INBOX.foo.barracuda");
    toexternal_helper("bloggs.com!user.jane", "user.jane@bloggs^com");
    toexternal_helper("bloggs.com!user.jane.baz", "user.jane@bloggs^com.baz");
    toexternal_helper("bloggs.com!user.jane.with^a^hat", "user.jane@bloggs^com.with^a^hat");
    toexternal_helper("bloggs.com!user.hatty^smith", "user.hatty^smith@bloggs^com");
    // no domains in shared folders in crossdomain
    toexternal_helper("bloggs.com!shared.quux", "shared.quux");

    toexternal_helper("boop.com!user.betty", "user.betty@boop^com");
    toexternal_helper("boop.com!user.betty.foo.bar", "user.betty@boop^com.foo.bar");
    toexternal_helper("boop.com!user.betty.foo^bar", "user.betty@boop^com.foo^bar");
    // shared folders in other domains can't be seen
    toexternal_helper("boop.com!shared.quux", NULL);
}

static void test_toexternal_crossdomains_alt(void)
{
    memset(&conf, 0, sizeof(conf));
    conf.altnamespace = 1;
    conf.virtdomains = 1;
    conf.crossdomains = 1;
    conf.unixhierarchysep = 1;
    conf.userprefix = "User Magic";
    conf.sharedprefix = "Shared Magic";
    conf.userid = "fred@bloggs.com";

    toexternal_helper("bloggs.com!user.fred", "INBOX");
    toexternal_helper("bloggs.com!user.fred.foo", "foo");
    toexternal_helper("bloggs.com!user.fred.foo.barracuda", "foo/barracuda");
    toexternal_helper("bloggs.com!user.jane", "User Magic/jane@bloggs.com");
    toexternal_helper("bloggs.com!user.jane.baz", "User Magic/jane@bloggs.com/baz");
    toexternal_helper("bloggs.com!user.jane.with^a^hat", "User Magic/jane@bloggs.com/with.a.hat");
    toexternal_helper("bloggs.com!user.hatty^smith", "User Magic/hatty.smith@bloggs.com");
    // domain on top-level folder in cross-domain
    toexternal_helper("bloggs.com!shared.quux", "Shared Magic/shared@bloggs.com/quux");

    toexternal_helper("boop.com!user.betty", "User Magic/betty@boop.com");
    toexternal_helper("boop.com!user.betty.foo.bar", "User Magic/betty@boop.com/foo/bar");
    toexternal_helper("boop.com!user.betty.foo^bar", "User Magic/betty@boop.com/foo.bar");
}

static void test_toexternal_crossdomains_alt_nouhs(void)
{
    memset(&conf, 0, sizeof(conf));
    conf.altnamespace = 1;
    conf.virtdomains = 1;
    conf.crossdomains = 1;
    conf.unixhierarchysep = 0;
    conf.userprefix = "User Magic";
    conf.sharedprefix = "Shared Magic";
    conf.userid = "fred@bloggs.com";

    toexternal_helper("bloggs.com!user.fred", "INBOX");
    toexternal_helper("bloggs.com!user.fred.foo", "foo");
    toexternal_helper("bloggs.com!user.fred.foo.barracuda", "foo.barracuda");
    toexternal_helper("bloggs.com!user.jane", "User Magic.jane@bloggs^com");
    toexternal_helper("bloggs.com!user.jane.baz", "User Magic.jane@bloggs^com.baz");
    toexternal_helper("bloggs.com!user.jane.with^a^hat", "User Magic.jane@bloggs^com.with^a^hat");
    toexternal_helper("bloggs.com!user.hatty^smith", "User Magic.hatty^smith@bloggs^com");
    // domains on top level shared folders in crossdomain
    toexternal_helper("bloggs.com!shared.quux", "Shared Magic.shared@bloggs^com.quux");

    toexternal_helper("boop.com!user.betty", "User Magic.betty@boop^com");
    toexternal_helper("boop.com!user.betty.foo.bar", "User Magic.betty@boop^com.foo.bar");
    toexternal_helper("boop.com!user.betty.foo^bar", "User Magic.betty@boop^com.foo^bar");
}

static void test_toexternal_specials(void)
{
    memset(&conf, 0, sizeof(conf));
    conf.userid = "fred";

    toexternal_helper("user.fred.*^", "INBOX.*^");

    conf.unixhierarchysep = 1;

    toexternal_helper("user.fred.*^", "INBOX/*.");

    conf.userid = "fred@bloggs.com";
    conf.virtdomains = 1;
    conf.unixhierarchysep = 0;

    toexternal_helper("bloggs.com!user.fred.*!^", "INBOX.*!^");

    conf.unixhierarchysep = 1;

    toexternal_helper("bloggs.com!user.fred.*!^", "INBOX/*!.");
}

static void test_virtdomains_at(void)
{
    memset(&conf, 0, sizeof(conf));
    conf.userid = "fred";

    toexternal_helper("user.fred.a@b", "INBOX.a@b");

    conf.unixhierarchysep = 1;

    toexternal_helper("user.fred.a@b", "INBOX/a@b");

    conf.userid = "fred@bloggs.com";
    conf.virtdomains = 1;
    conf.unixhierarchysep = 0;

    toexternal_helper("bloggs.com!user.fred.a@b", "INBOX.a@b");

    conf.unixhierarchysep = 1;

    toexternal_helper("bloggs.com!user.fred.a@b", "INBOX/a@b");

    conf.defdomain = "bloggs.com";
    conf.userid = "fred";

    conf.unixhierarchysep = 0;

    toexternal_helper("user.fred.a@b", "INBOX.a@b");

    conf.unixhierarchysep = 1;

    toexternal_helper("user.fred.a@b", "INBOX/a@b");
}

static void test_toexternal_unixhier(void)
{
    memset(&conf, 0, sizeof(conf));
    conf.unixhierarchysep = 1;
    conf.userid = "fred";

    toexternal_helper("user.fred", "INBOX");
    toexternal_helper("user.fred.foo", "INBOX/foo");
    toexternal_helper("user.fred.foo.barracuda", "INBOX/foo/barracuda");
    toexternal_helper("user.jane", "user/jane");
    toexternal_helper("user.jane.baz", "user/jane/baz");
    toexternal_helper("user.joe^bloggs.mega", "user/joe.bloggs/mega");
    toexternal_helper("shared.quux", "shared/quux");

    /* check that @ doesn't get polluted */
    toexternal_helper("user.fred.folder@place", "INBOX/folder@place");
    toexternal_helper("user.fred^smith@example^com.folder@place", "user/fred.smith@example.com/folder@place");

    /* can't have a hat on the outside */
    toexternal_helper(NULL, "user/fred^smith");
}

static void test_toexternal_alt(void)
{
    memset(&conf, 0, sizeof(conf));
    conf.altnamespace = 1;
    conf.userprefix = "Uvvers";
    conf.sharedprefix = "Chaired";
    conf.userid = "fred";

    toexternal_helper("user.fred", "INBOX");
    toexternal_helper("user.fred.foo", "foo");
    toexternal_helper("user.fred.foo.barracuda", "foo.barracuda");
    toexternal_helper("user.jane", "Uvvers.jane");
    toexternal_helper("user.jane.baz", "Uvvers.jane.baz");
    toexternal_helper("shared.quux", "Chaired.shared.quux");
}

static void test_contains_parent(void)
{
    CU_ASSERT_EQUAL(0, mboxname_contains_parent("user.foo.B", "user"));
    CU_ASSERT_EQUAL(1, mboxname_contains_parent("user.foo.B", "user.foo"));
    CU_ASSERT_EQUAL(1, mboxname_contains_parent("user.foo.B", "user.foo.A"));
    CU_ASSERT_EQUAL(1, mboxname_contains_parent("user.foo.B", "user.foo.A.sub"));
    CU_ASSERT_EQUAL(0, mboxname_contains_parent("user.foo.B.sub", "user.foo"));
    CU_ASSERT_EQUAL(0, mboxname_contains_parent("user.foo.B.sub", "user.foo.A"));
    CU_ASSERT_EQUAL(0, mboxname_contains_parent("user.foo.B.sub", "user.foo.A.sub"));
    CU_ASSERT_EQUAL(0, mboxname_contains_parent("user.foo.BA.sub", "user.foo.B.sub"));
    CU_ASSERT_EQUAL(0, mboxname_contains_parent("user.foo.B.A", "user.foo.BA"));
    // this one is a bit fun - shared has no parent, so should be OK anywhere
    CU_ASSERT_EQUAL(0, mboxname_contains_parent("shared", "user.foo.A"));
}

static void test_nextmodseq(void)
{
    static const char FREDNAME[] = "bloggs.com!user.fred";
    mbname_t *mbname = NULL;
    char *fname;

    imapopts[IMAPOPT_CONVERSATIONS].val.b = 1;

    /* ensure there is no file */
    mbname = mbname_from_intname(FREDNAME);
    fname = mboxname_conf_getpath(mbname, "modseq");
    xunlink(fname);
    free(fname);
    mbname_free(&mbname);

    /* initial value should be 1 without file */
    CU_ASSERT_EQUAL(mboxname_nextmodseq(FREDNAME, 0, 0, 0), 1);
    /* next value should always increment */
    CU_ASSERT_EQUAL(mboxname_nextmodseq(FREDNAME, 0, 0, 0), 2);
    /* higher value should force a jump */
    CU_ASSERT_EQUAL(mboxname_nextmodseq(FREDNAME, 100, 0, 0), 101);
    /* lower value should not decrease */
    CU_ASSERT_EQUAL(mboxname_nextmodseq(FREDNAME, 5, 0, 0), 102);
}

static void test_common_ancestor(void)
{
    char *ancestor;

    ancestor = mboxname_common_ancestor("user.foo.A.B", "user.foo.A");
    CU_ASSERT_PTR_NULL(ancestor);

    ancestor = mboxname_common_ancestor("user.foo.A.B", "user.foo.C.D");
    CU_ASSERT_PTR_NULL(ancestor);

    ancestor = mboxname_common_ancestor("user.foo.A.B.C", "user.foo.A.B");
    CU_ASSERT_STRING_EQUAL("user.foo.A", ancestor);
    free(ancestor);

    ancestor = mboxname_common_ancestor("user.foo.A.B.C", "user.foo.A.X.Y");
    CU_ASSERT_STRING_EQUAL("user.foo.A", ancestor);
    free(ancestor);

    ancestor = mboxname_common_ancestor("user.foo.A.B.C", "user.bar.A.B");
    CU_ASSERT_PTR_NULL(ancestor);

    ancestor = mboxname_common_ancestor("user.foo.A.B.C", "user.bar.A.X.Y");
    CU_ASSERT_PTR_NULL(ancestor);
}

#define mboxname_isa_helper(isa, results) do {                          \
    int *r = results;                                                   \
                                                                        \
    imapopts[IMAPOPT_NOTESMAILBOX].val.s = "Notes";                     \
                                                                        \
    CU_ASSERT_EQUAL(*r++, isa("user.foo", 0));                          \
    CU_ASSERT_EQUAL(*r++, isa("user.foo.A", 0));                        \
    CU_ASSERT_EQUAL(*r++, isa("user.foo.Notes", 0));                    \
    CU_ASSERT_EQUAL(*r++, isa("user.foo.#addressbooks", 0));            \
    CU_ASSERT_EQUAL(*r++, isa("user.foo.#addressbooks",                 \
                              MBTYPE_COLLECTION));                      \
    CU_ASSERT_EQUAL(*r++, isa("user.foo.#addressbooks.A", 0));          \
    CU_ASSERT_EQUAL(*r++, isa("user.foo.#addressbooks.A",               \
                              MBTYPE_ADDRESSBOOK));                     \
    CU_ASSERT_EQUAL(*r++, isa("user.foo.#calendars", 0));               \
    CU_ASSERT_EQUAL(*r++, isa("user.foo.#calendars",                    \
                              MBTYPE_COLLECTION));                      \
    CU_ASSERT_EQUAL(*r++, isa("user.foo.#calendars.A", 0));             \
    CU_ASSERT_EQUAL(*r++, isa("user.foo.#calendars.A",                  \
                              MBTYPE_CALENDAR));                        \
    CU_ASSERT_EQUAL(*r++, isa("user.foo.#drive", 0));                   \
    CU_ASSERT_EQUAL(*r++, isa("user.foo.#drive",                        \
                              MBTYPE_COLLECTION));                      \
    CU_ASSERT_EQUAL(*r++, isa("user.foo.#drive.A", 0));                 \
    CU_ASSERT_EQUAL(*r++, isa("user.foo.#drive.A",                      \
                              MBTYPE_COLLECTION));                      \
    CU_ASSERT_EQUAL(*r++, isa("user.foo.#notifications", 0));           \
    CU_ASSERT_EQUAL(*r++, isa("user.foo.#notifications",                \
                              MBTYPE_COLLECTION));                      \
    CU_ASSERT_EQUAL(*r++, isa("user.foo.#jmap", 0));                    \
    CU_ASSERT_EQUAL(*r++, isa("user.foo.#jmap",                         \
                              MBTYPE_COLLECTION));                      \
    CU_ASSERT_EQUAL(*r++, isa("user.foo.#jmapnotification", 0));        \
    CU_ASSERT_EQUAL(*r++, isa("user.foo.#jmapnotification",             \
                              MBTYPE_JMAPNOTIFY));                      \
    CU_ASSERT_EQUAL(*r++, isa("user.foo.#jmappushsubscription", 0));    \
    CU_ASSERT_EQUAL(*r++, isa("user.foo.#jmappushsubscription",         \
                              MBTYPE_JMAPPUSHSUB));                     \
    CU_ASSERT_EQUAL(*r++, isa("user.foo.#jmapsubmission", 0));          \
    CU_ASSERT_EQUAL(*r++, isa("user.foo.#jmapsubmission",               \
                              MBTYPE_JMAPSUBMIT));                      \
    CU_ASSERT_EQUAL(*r++, isa("user.foo.#sieve", 0));                   \
    CU_ASSERT_EQUAL(*r++, isa("user.foo.#sieve",                        \
                              MBTYPE_SIEVE));                           \
} while(0)

static void test_isnotesmailbox(void)
{
    int results[] = {
        /* Email         */  0, 0,        /* Notes              */  1,
        /* Addressbook   */  0, 0, 0, 0,  /* Calendar           */  0, 0, 0, 0,
        /* DAV drive     */  0, 0, 0, 0,  /* DAV Notifications  */  0, 0,
        /* JMAP Upload   */  0, 0,        /* JMAP Notifications */  0, 0,
        /* JMAP Push Sub */  0, 0,        /* JMAP Submissions   */  0, 0,
        /* Sieve         */  0, 0
    };

    mboxname_isa_helper(mboxname_isnotesmailbox, results);
}

static void test_iscalendarmailbox(void)
{
    int results[] = {
        /* Email         */  0, 0,        /* Notes              */  0,
        /* Addressbook   */  0, 0, 0, 0,  /* Calendar           */  1, 1, 1, 1,
        /* DAV drive     */  0, 0, 0, 0,  /* DAV Notifications  */  0, 0,
        /* JMAP Upload   */  0, 0,        /* JMAP Notifications */  0, 0,
        /* JMAP Push Sub */  0, 0,        /* JMAP Submissions   */  0, 0,
        /* Sieve         */  0, 0
    };

    mboxname_isa_helper(mboxname_iscalendarmailbox, results);
}

static void test_isaddressbookmailbox(void)
{
    int results[] = {
        /* Email         */  0, 0,        /* Notes              */  0,
        /* Addressbook   */  1, 1, 1, 1,  /* Calendar           */  0, 0, 0, 0,
        /* DAV drive     */  0, 0, 0, 0,  /* DAV Notifications  */  0, 0,
        /* JMAP Upload   */  0, 0,        /* JMAP Notifications */  0, 0,
        /* JMAP Push Sub */  0, 0,        /* JMAP Submissions   */  0, 0,
        /* Sieve         */  0, 0
    };

    mboxname_isa_helper(mboxname_isaddressbookmailbox, results);
}

static void test_isdavdrivemailbox(void)
{
    int results[] = {
        /* Email         */  0, 0,        /* Notes              */  0,
        /* Addressbook   */  0, 0, 0, 0,  /* Calendar           */  0, 0, 0, 0,
        /* DAV drive     */  1, 1, 1, 1,  /* DAV Notifications  */  0, 0,
        /* JMAP Upload   */  0, 0,        /* JMAP Notifications */  0, 0,
        /* JMAP Push Sub */  0, 0,        /* JMAP Submissions   */  0, 0,
        /* Sieve         */  0, 0
    };

    mboxname_isa_helper(mboxname_isdavdrivemailbox, results);
}

static void test_isdavnotificationsmailbox(void)
{
    int results[] = {
        /* Email         */  0, 0,        /* Notes              */  0,
        /* Addressbook   */  0, 0, 0, 0,  /* Calendar           */  0, 0, 0, 0,
        /* DAV drive     */  0, 0, 0, 0,  /* DAV Notifications  */  1, 1,
        /* JMAP Upload   */  0, 0,        /* JMAP Notifications */  0, 0,
        /* JMAP Push Sub */  0, 0,        /* JMAP Submissions   */  0, 0,
        /* Sieve         */  0, 0
    };

    mboxname_isa_helper(mboxname_isdavnotificationsmailbox, results);
}

static void test_isjmapuploadmailbox(void)
{
    int results[] = {
        /* Email         */  0, 0,        /* Notes              */  0,
        /* Addressbook   */  0, 0, 0, 0,  /* Calendar           */  0, 0, 0, 0,
        /* DAV drive     */  0, 0, 0, 0,  /* DAV Notifications  */  0, 0,
        /* JMAP Upload   */  1, 1,        /* JMAP Notifications */  0, 0,
        /* JMAP Push Sub */  0, 0,        /* JMAP Submissions   */  0, 0,
        /* Sieve         */  0, 0
    };

    mboxname_isa_helper(mboxname_isjmapuploadmailbox, results);
}

static void test_isjmapnotificationsmailbox(void)
{
    int results[] = {
        /* Email         */  0, 0,        /* Notes              */  0,
        /* Addressbook   */  0, 0, 0, 0,  /* Calendar           */  0, 0, 0, 0,
        /* DAV drive     */  0, 0, 0, 0,  /* DAV Notifications  */  0, 0,
        /* JMAP Upload   */  0, 0,        /* JMAP Notifications */  1, 1,
        /* JMAP Push Sub */  0, 0,        /* JMAP Submissions   */  0, 0,
        /* Sieve         */  0, 0
    };

    mboxname_isa_helper(mboxname_isjmapnotificationsmailbox, results);
}

static void test_ispushsubscriptionmailbox(void)
{
    int results[] = {
        /* Email         */  0, 0,        /* Notes              */  0,
        /* Addressbook   */  0, 0, 0, 0,  /* Calendar           */  0, 0, 0, 0,
        /* DAV drive     */  0, 0, 0, 0,  /* DAV Notifications  */  0, 0,
        /* JMAP Upload   */  0, 0,        /* JMAP Notifications */  0, 0,
        /* JMAP Push Sub */  1, 1,        /* JMAP Submissions   */  0, 0,
        /* Sieve         */  0, 0
    };

    mboxname_isa_helper(mboxname_ispushsubscriptionmailbox, results);
}

static void test_issubmissionmailbox(void)
{
    int results[] = {
        /* Email         */  0, 0,        /* Notes              */  0,
        /* Addressbook   */  0, 0, 0, 0,  /* Calendar           */  0, 0, 0, 0,
        /* DAV drive     */  0, 0, 0, 0,  /* DAV Notifications  */  0, 0,
        /* JMAP Upload   */  0, 0,        /* JMAP Notifications */  0, 0,
        /* JMAP Push Sub */  0, 0,        /* JMAP Submissions   */  1, 1,
        /* Sieve         */  0, 0
    };

    mboxname_isa_helper(mboxname_issubmissionmailbox, results);
}

static void test_issievemailbox(void)
{
    int results[] = {
        /* Email         */  0, 0,        /* Notes              */  0,
        /* Addressbook   */  0, 0, 0, 0,  /* Calendar           */  0, 0, 0, 0,
        /* DAV drive     */  0, 0, 0, 0,  /* DAV Notifications  */  0, 0,
        /* JMAP Upload   */  0, 0,        /* JMAP Notifications */  0, 0,
        /* JMAP Push Sub */  0, 0,        /* JMAP Submissions   */  0, 0,
        /* Sieve         */  1, 1
    };

    mboxname_isa_helper(mboxname_issievemailbox, results);
}

static void test_isnonimapmailbox(void)
{
    int results[] = {
        /* Email         */  0, 0,        /* Notes              */  0,
        /* Addressbook   */  1, 1, 1, 1,  /* Calendar           */  1, 1, 1, 1,
        /* DAV drive     */  1, 1, 1, 1,  /* DAV Notifications  */  1, 1,
        /* JMAP Upload   */  1, 1,        /* JMAP Notifications */  1, 1,
        /* JMAP Push Sub */  1, 1,        /* JMAP Submissions   */  1, 1,
        /* Sieve         */  1, 1
    };

    mboxname_isa_helper(mboxname_isnonimapmailbox, results);
}

static void test_isnondeliverymailbox(void)
{
    int results[] = {
        /* Email         */  0, 0,        /* Notes              */  1,
        /* Addressbook   */  1, 1, 1, 1,  /* Calendar           */  1, 1, 1, 1,
        /* DAV drive     */  1, 1, 1, 1,  /* DAV Notifications  */  1, 1,
        /* JMAP Upload   */  1, 1,        /* JMAP Notifications */  1, 1,
        /* JMAP Push Sub */  1, 1,        /* JMAP Submissions   */  1, 1,
        /* Sieve         */  1, 1
    };

    mboxname_isa_helper(mboxname_isnondeliverymailbox, results);

    CU_ASSERT_EQUAL(1, mboxname_isdeletedmailbox("DELETED.user.foo.A", NULL));
}



static enum enum_value old_config_virtdomains;
static union config_value old_config_unixhierarchysep;
static union config_value old_config_altnamespace;
static union config_value old_config_userprefix;
static union config_value old_config_sharedprefix;
static union config_value old_config_conversations;
static const char *old_config_defdomain;

static int set_up(void)
{
    /* need basic configuration */
    libcyrus_config_setstring(CYRUSOPT_CONFIG_DIR, DBDIR);
    config_read_string(
        "configdirectory: "DBDIR"/conf\n"
    );

    /*
     * TODO: this is pretty hacky.  There should be some
     * cleaner way of pushing aside the config for a moment
     * and temporarily setting up a particular set of config
     * options for testing.
     */
    old_config_virtdomains = config_virtdomains;
    config_virtdomains = IMAP_ENUM_VIRTDOMAINS_ON;

    old_config_defdomain = config_defdomain;

    old_config_unixhierarchysep = imapopts[IMAPOPT_UNIXHIERARCHYSEP].val;
    old_config_altnamespace = imapopts[IMAPOPT_ALTNAMESPACE].val;
    old_config_userprefix = imapopts[IMAPOPT_USERPREFIX].val;
    old_config_sharedprefix = imapopts[IMAPOPT_SHAREDPREFIX].val;
    old_config_conversations = imapopts[IMAPOPT_CONVERSATIONS].val;

    return 0;
}

static int tear_down(void)
{
    int r;

    config_reset();

    r = system("rm -rf " DBDIR);

    return r;
}

/* vim: set ft=c: */
