depot/third_party/nixpkgs/nixos/tests/vdirsyncer.nix

300 lines
8.7 KiB
Nix

import ./make-test-python.nix (
{ pkgs, lib, ... }:
let
radicale_calendars = {
type = "caldav";
url = "http://localhost:5232/";
# Radicale needs username/password.
username = "alice";
password = "password";
};
radicale_contacts = {
type = "carddav";
url = "http://localhost:5232/";
# Radicale needs username/password.
username = "alice";
password = "password";
};
xandikos_calendars = {
type = "caldav";
url = "http://localhost:8080/user/calendars";
# Xandikos warns
# > No current-user-principal returned, re-using URL http://localhost:8080/user/calendars/
# but we do not need username/password.
};
xandikos_contacts = {
type = "carddav";
url = "http://localhost:8080/user/contacts";
};
local_calendars = {
type = "filesystem";
path = "~/calendars";
fileext = ".ics";
};
local_contacts = {
type = "filesystem";
path = "~/contacts";
fileext = ".vcf";
};
mkPairs = a: b: {
calendars = {
a = "${a}_calendars";
b = "${b}_calendars";
collections = [
"from a"
"from b"
];
};
contacts = {
a = "${a}_contacts";
b = "${b}_contacts";
collections = [
"from a"
"from b"
];
};
};
mkRadicaleProps =
tag:
pkgs.writeText "Radicale.props" (
builtins.toJSON {
inherit tag;
}
);
writeLines =
name: eol: lines:
pkgs.writeText name (lib.concatMapStrings (l: "${l}${eol}") lines);
prodid = "-//NixOS test//EN";
dtstamp = "20231129T194743Z";
writeICS =
{
uid,
summary,
dtstart,
dtend,
}:
writeLines "${uid}.ics" "\r\n" [
"BEGIN:VCALENDAR"
"VERSION:2.0"
"PRODID:${prodid}"
"BEGIN:VEVENT"
"UID:${uid}"
"SUMMARY:${summary}"
"DTSTART:${dtstart}"
"DTEND:${dtend}"
"DTSTAMP:${dtstamp}"
"END:VEVENT"
"END:VCALENDAR"
];
foo_ics = writeICS {
uid = "foo";
summary = "Epochalypse";
dtstart = "19700101T000000Z";
dtend = "20380119T031407Z";
};
bar_ics = writeICS {
uid = "bar";
summary = "One Billion Seconds";
dtstart = "19700101T000000Z";
dtend = "20010909T014640Z";
};
writeVCF =
{
uid,
name,
displayName,
email,
}:
writeLines "${uid}.vcf" "\r\n" [
# One of the tools enforces this order of fields.
"BEGIN:VCARD"
"VERSION:4.0"
"UID:${uid}"
"EMAIL;TYPE=INTERNET:${email}"
"FN:${displayName}"
"N:${name}"
"END:VCARD"
];
foo_vcf = writeVCF {
uid = "foo";
name = "Doe;John;;;";
displayName = "John Doe";
email = "john.doe@example.org";
};
bar_vcf = writeVCF {
uid = "bar";
name = "Doe;Jane;;;";
displayName = "Jane Doe";
email = "jane.doe@example.org";
};
in
{
name = "vdirsyncer";
meta.maintainers = with lib.maintainers; [ schnusch ];
nodes = {
machine = {
services.radicale = {
enable = true;
settings.auth.type = "none";
};
services.xandikos = {
enable = true;
extraOptions = [ "--autocreate" ];
};
services.vdirsyncer = {
enable = true;
jobs = {
alice = {
user = "alice";
group = "users";
config = {
statusPath = "/home/alice/.vdirsyncer";
storages = {
inherit
local_calendars
local_contacts
radicale_calendars
radicale_contacts
;
};
pairs = mkPairs "local" "radicale";
};
forceDiscover = true;
};
bob = {
user = "bob";
group = "users";
config = {
statusPath = "/home/bob/.vdirsyncer";
storages = {
inherit
local_calendars
local_contacts
xandikos_calendars
xandikos_contacts
;
};
pairs = mkPairs "local" "xandikos";
};
forceDiscover = true;
};
remote = {
config = {
storages = {
inherit
radicale_calendars
radicale_contacts
xandikos_calendars
xandikos_contacts
;
};
pairs = mkPairs "radicale" "xandikos";
};
forceDiscover = true;
};
};
};
users.users = {
alice.isNormalUser = true;
bob.isNormalUser = true;
};
};
};
testScript = ''
def run_unit(name):
machine.systemctl(f"start {name}")
# The service is Type=oneshot without RemainAfterExit=yes. Once it
# is finished it is no longer active and wait_for_unit will fail.
# When that happens we check if it actually failed.
try:
machine.wait_for_unit(name)
except:
machine.fail(f"systemctl is-failed {name}")
start_all()
machine.wait_for_open_port(5232)
machine.wait_for_open_port(8080)
machine.wait_for_unit("multi-user.target")
with subtest("alice -> radicale"):
# vdirsyncer cannot create create collections on Radicale,
# see https://vdirsyncer.pimutils.org/en/stable/tutorials/radicale.html
machine.succeed("runuser -u radicale -- install -Dm 644 ${mkRadicaleProps "VCALENDAR"} /var/lib/radicale/collections/collection-root/alice/foocal/.Radicale.props")
machine.succeed("runuser -u radicale -- install -Dm 644 ${mkRadicaleProps "VADDRESSBOOK"} /var/lib/radicale/collections/collection-root/alice/foocard/.Radicale.props")
machine.succeed("runuser -u alice -- install -Dm 644 ${foo_ics} /home/alice/calendars/foocal/foo.ics")
machine.succeed("runuser -u alice -- install -Dm 644 ${foo_vcf} /home/alice/contacts/foocard/foo.vcf")
run_unit("vdirsyncer@alice.service")
# test statusPath
machine.succeed("test -d /home/alice/.vdirsyncer")
machine.fail("test -e /var/lib/private/vdirsyncer/alice")
with subtest("bob -> xandikos"):
# I suspect Radicale shares the namespace for calendars and
# contacts, but Xandikos separates them. We just use `barcal` and
# `barcard` with Xandikos as well to avoid conflicts.
machine.succeed("runuser -u bob -- install -Dm 644 ${bar_ics} /home/bob/calendars/barcal/bar.ics")
machine.succeed("runuser -u bob -- install -Dm 644 ${bar_vcf} /home/bob/contacts/barcard/bar.vcf")
run_unit("vdirsyncer@bob.service")
# test statusPath
machine.succeed("test -d /home/bob/.vdirsyncer")
machine.fail("test -e /var/lib/private/vdirsyncer/bob")
with subtest("radicale <-> xandikos"):
# vdirsyncer cannot create create collections on Radicale,
# see https://vdirsyncer.pimutils.org/en/stable/tutorials/radicale.html
machine.succeed("runuser -u radicale -- install -Dm 644 ${mkRadicaleProps "VCALENDAR"} /var/lib/radicale/collections/collection-root/alice/barcal/.Radicale.props")
machine.succeed("runuser -u radicale -- install -Dm 644 ${mkRadicaleProps "VADDRESSBOOK"} /var/lib/radicale/collections/collection-root/alice/barcard/.Radicale.props")
run_unit("vdirsyncer@remote.service")
# test statusPath
machine.succeed("test -d /var/lib/private/vdirsyncer/remote")
with subtest("radicale -> alice"):
run_unit("vdirsyncer@alice.service")
with subtest("xandikos -> bob"):
run_unit("vdirsyncer@bob.service")
with subtest("compare synced files"):
# iCalendar files get reordered
machine.succeed("diff -u --strip-trailing-cr <(sort /home/alice/calendars/foocal/foo.ics) <(sort /home/bob/calendars/foocal/foo.ics) >&2")
machine.succeed("diff -u --strip-trailing-cr <(sort /home/bob/calendars/barcal/bar.ics) <(sort /home/alice/calendars/barcal/bar.ics) >&2")
machine.succeed("diff -u --strip-trailing-cr /home/alice/contacts/foocard/foo.vcf /home/bob/contacts/foocard/foo.vcf >&2")
machine.succeed("diff -u --strip-trailing-cr /home/bob/contacts/barcard/bar.vcf /home/alice/contacts/barcard/bar.vcf >&2")
'';
}
)