at 25.11-pre 9.0 kB view raw
1import ./make-test-python.nix ( 2 { pkgs, lib, ... }: 3 4 let 5 6 radicale_calendars = { 7 type = "caldav"; 8 url = "http://localhost:5232/"; 9 # Radicale needs username/password. 10 username = "alice"; 11 password = "password"; 12 }; 13 14 radicale_contacts = { 15 type = "carddav"; 16 url = "http://localhost:5232/"; 17 # Radicale needs username/password. 18 username = "alice"; 19 password = "password"; 20 }; 21 22 xandikos_calendars = { 23 type = "caldav"; 24 url = "http://localhost:8080/user/calendars"; 25 # Xandikos warns 26 # > No current-user-principal returned, re-using URL http://localhost:8080/user/calendars/ 27 # but we do not need username/password. 28 }; 29 30 xandikos_contacts = { 31 type = "carddav"; 32 url = "http://localhost:8080/user/contacts"; 33 }; 34 35 local_calendars = { 36 type = "filesystem"; 37 path = "~/calendars"; 38 fileext = ".ics"; 39 }; 40 41 local_contacts = { 42 type = "filesystem"; 43 path = "~/contacts"; 44 fileext = ".vcf"; 45 }; 46 47 mkPairs = a: b: { 48 calendars = { 49 a = "${a}_calendars"; 50 b = "${b}_calendars"; 51 collections = [ 52 "from a" 53 "from b" 54 ]; 55 }; 56 contacts = { 57 a = "${a}_contacts"; 58 b = "${b}_contacts"; 59 collections = [ 60 "from a" 61 "from b" 62 ]; 63 }; 64 }; 65 66 mkRadicaleProps = 67 tag: 68 pkgs.writeText "Radicale.props" ( 69 builtins.toJSON { 70 inherit tag; 71 } 72 ); 73 74 writeLines = 75 name: eol: lines: 76 pkgs.writeText name (lib.concatMapStrings (l: "${l}${eol}") lines); 77 78 prodid = "-//NixOS test//EN"; 79 dtstamp = "20231129T194743Z"; 80 81 writeICS = 82 { 83 uid, 84 summary, 85 dtstart, 86 dtend, 87 }: 88 writeLines "${uid}.ics" "\r\n" [ 89 "BEGIN:VCALENDAR" 90 "VERSION:2.0" 91 "PRODID:${prodid}" 92 "BEGIN:VEVENT" 93 "UID:${uid}" 94 "SUMMARY:${summary}" 95 "DTSTART:${dtstart}" 96 "DTEND:${dtend}" 97 "DTSTAMP:${dtstamp}" 98 "END:VEVENT" 99 "END:VCALENDAR" 100 ]; 101 102 foo_ics = writeICS { 103 uid = "foo"; 104 summary = "Epochalypse"; 105 dtstart = "19700101T000000Z"; 106 dtend = "20380119T031407Z"; 107 }; 108 109 bar_ics = writeICS { 110 uid = "bar"; 111 summary = "One Billion Seconds"; 112 dtstart = "19700101T000000Z"; 113 dtend = "20010909T014640Z"; 114 }; 115 116 writeVCF = 117 { 118 uid, 119 name, 120 displayName, 121 email, 122 }: 123 writeLines "${uid}.vcf" "\r\n" [ 124 # One of the tools enforces this order of fields. 125 "BEGIN:VCARD" 126 "VERSION:4.0" 127 "UID:${uid}" 128 "EMAIL;TYPE=INTERNET:${email}" 129 "FN:${displayName}" 130 "N:${name}" 131 "END:VCARD" 132 ]; 133 134 foo_vcf = writeVCF { 135 uid = "foo"; 136 name = "Doe;John;;;"; 137 displayName = "John Doe"; 138 email = "john.doe@example.org"; 139 }; 140 141 bar_vcf = writeVCF { 142 uid = "bar"; 143 name = "Doe;Jane;;;"; 144 displayName = "Jane Doe"; 145 email = "jane.doe@example.org"; 146 }; 147 148 in 149 { 150 name = "vdirsyncer"; 151 152 meta.maintainers = with lib.maintainers; [ schnusch ]; 153 154 nodes = { 155 machine = { 156 services.radicale = { 157 enable = true; 158 settings.auth.type = "none"; 159 }; 160 161 services.xandikos = { 162 enable = true; 163 extraOptions = [ "--autocreate" ]; 164 }; 165 166 services.vdirsyncer = { 167 enable = true; 168 jobs = { 169 170 alice = { 171 user = "alice"; 172 group = "users"; 173 config = { 174 statusPath = "/home/alice/.vdirsyncer"; 175 storages = { 176 inherit 177 local_calendars 178 local_contacts 179 radicale_calendars 180 radicale_contacts 181 ; 182 }; 183 pairs = mkPairs "local" "radicale"; 184 }; 185 forceDiscover = true; 186 }; 187 188 bob = { 189 user = "bob"; 190 group = "users"; 191 config = { 192 statusPath = "/home/bob/.vdirsyncer"; 193 storages = { 194 inherit 195 local_calendars 196 local_contacts 197 xandikos_calendars 198 xandikos_contacts 199 ; 200 }; 201 pairs = mkPairs "local" "xandikos"; 202 }; 203 forceDiscover = true; 204 }; 205 206 remote = { 207 config = { 208 storages = { 209 inherit 210 radicale_calendars 211 radicale_contacts 212 xandikos_calendars 213 xandikos_contacts 214 ; 215 }; 216 pairs = mkPairs "radicale" "xandikos"; 217 }; 218 forceDiscover = true; 219 }; 220 221 }; 222 }; 223 224 users.users = { 225 alice.isNormalUser = true; 226 bob.isNormalUser = true; 227 }; 228 }; 229 }; 230 231 testScript = '' 232 def run_unit(name): 233 machine.systemctl(f"start {name}") 234 # The service is Type=oneshot without RemainAfterExit=yes. Once it 235 # is finished it is no longer active and wait_for_unit will fail. 236 # When that happens we check if it actually failed. 237 try: 238 machine.wait_for_unit(name) 239 except: 240 machine.fail(f"systemctl is-failed {name}") 241 242 start_all() 243 244 machine.wait_for_open_port(5232) 245 machine.wait_for_open_port(8080) 246 machine.wait_for_unit("multi-user.target") 247 248 with subtest("alice -> radicale"): 249 # vdirsyncer cannot create create collections on Radicale, 250 # see https://vdirsyncer.pimutils.org/en/stable/tutorials/radicale.html 251 machine.succeed("runuser -u radicale -- install -Dm 644 ${mkRadicaleProps "VCALENDAR"} /var/lib/radicale/collections/collection-root/alice/foocal/.Radicale.props") 252 machine.succeed("runuser -u radicale -- install -Dm 644 ${mkRadicaleProps "VADDRESSBOOK"} /var/lib/radicale/collections/collection-root/alice/foocard/.Radicale.props") 253 254 machine.succeed("runuser -u alice -- install -Dm 644 ${foo_ics} /home/alice/calendars/foocal/foo.ics") 255 machine.succeed("runuser -u alice -- install -Dm 644 ${foo_vcf} /home/alice/contacts/foocard/foo.vcf") 256 run_unit("vdirsyncer@alice.service") 257 258 # test statusPath 259 machine.succeed("test -d /home/alice/.vdirsyncer") 260 machine.fail("test -e /var/lib/private/vdirsyncer/alice") 261 262 with subtest("bob -> xandikos"): 263 # I suspect Radicale shares the namespace for calendars and 264 # contacts, but Xandikos separates them. We just use `barcal` and 265 # `barcard` with Xandikos as well to avoid conflicts. 266 machine.succeed("runuser -u bob -- install -Dm 644 ${bar_ics} /home/bob/calendars/barcal/bar.ics") 267 machine.succeed("runuser -u bob -- install -Dm 644 ${bar_vcf} /home/bob/contacts/barcard/bar.vcf") 268 run_unit("vdirsyncer@bob.service") 269 270 # test statusPath 271 machine.succeed("test -d /home/bob/.vdirsyncer") 272 machine.fail("test -e /var/lib/private/vdirsyncer/bob") 273 274 with subtest("radicale <-> xandikos"): 275 # vdirsyncer cannot create create collections on Radicale, 276 # see https://vdirsyncer.pimutils.org/en/stable/tutorials/radicale.html 277 machine.succeed("runuser -u radicale -- install -Dm 644 ${mkRadicaleProps "VCALENDAR"} /var/lib/radicale/collections/collection-root/alice/barcal/.Radicale.props") 278 machine.succeed("runuser -u radicale -- install -Dm 644 ${mkRadicaleProps "VADDRESSBOOK"} /var/lib/radicale/collections/collection-root/alice/barcard/.Radicale.props") 279 280 run_unit("vdirsyncer@remote.service") 281 282 # test statusPath 283 machine.succeed("test -d /var/lib/private/vdirsyncer/remote") 284 285 with subtest("radicale -> alice"): 286 run_unit("vdirsyncer@alice.service") 287 288 with subtest("xandikos -> bob"): 289 run_unit("vdirsyncer@bob.service") 290 291 with subtest("compare synced files"): 292 # iCalendar files get reordered 293 machine.succeed("diff -u --strip-trailing-cr <(sort /home/alice/calendars/foocal/foo.ics) <(sort /home/bob/calendars/foocal/foo.ics) >&2") 294 machine.succeed("diff -u --strip-trailing-cr <(sort /home/bob/calendars/barcal/bar.ics) <(sort /home/alice/calendars/barcal/bar.ics) >&2") 295 296 machine.succeed("diff -u --strip-trailing-cr /home/alice/contacts/foocard/foo.vcf /home/bob/contacts/foocard/foo.vcf >&2") 297 machine.succeed("diff -u --strip-trailing-cr /home/bob/contacts/barcard/bar.vcf /home/alice/contacts/barcard/bar.vcf >&2") 298 ''; 299 } 300)